activerecord 7.1.4.1 → 7.2.2.1
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 +643 -2274
- 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 +15 -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 +7 -1
- data/lib/active_record/associations/collection_proxy.rb +14 -1
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +7 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
- data/lib/active_record/associations/join_dependency.rb +4 -4
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +2 -1
- data/lib/active_record/associations/preloader/branch.rb +7 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/singular_association.rb +6 -0
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +59 -292
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/primary_key.rb +23 -55
- data/lib/active_record/attribute_methods/read.rb +1 -13
- data/lib/active_record/attribute_methods/serialization.rb +4 -24
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
- data/lib/active_record/attribute_methods.rb +54 -63
- data/lib/active_record/attributes.rb +61 -47
- data/lib/active_record/autosave_association.rb +12 -29
- 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 +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 +270 -65
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +189 -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 +15 -6
- data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -62
- data/lib/active_record/connection_adapters/abstract_adapter.rb +24 -44
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +40 -10
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
- data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +6 -0
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +16 -15
- 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 +17 -11
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +29 -24
- 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 +125 -75
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +15 -15
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -48
- data/lib/active_record/connection_adapters.rb +121 -0
- data/lib/active_record/connection_handling.rb +56 -41
- data/lib/active_record/core.rb +86 -38
- 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 +19 -4
- data/lib/active_record/database_configurations/hash_config.rb +38 -34
- data/lib/active_record/database_configurations/url_config.rb +20 -1
- data/lib/active_record/database_configurations.rb +1 -1
- data/lib/active_record/delegated_type.rb +24 -0
- data/lib/active_record/dynamic_matchers.rb +2 -2
- data/lib/active_record/encryption/encryptable_record.rb +3 -3
- data/lib/active_record/encryption/encrypted_attribute_type.rb +24 -4
- data/lib/active_record/encryption/encryptor.rb +18 -3
- 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.rb +2 -0
- data/lib/active_record/enum.rb +19 -2
- data/lib/active_record/errors.rb +46 -20
- 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 +2 -2
- data/lib/active_record/inheritance.rb +4 -2
- data/lib/active_record/insert_all.rb +18 -15
- data/lib/active_record/integration.rb +4 -1
- data/lib/active_record/internal_metadata.rb +48 -34
- data/lib/active_record/locking/optimistic.rb +7 -6
- data/lib/active_record/log_subscriber.rb +0 -21
- data/lib/active_record/marshalling.rb +4 -1
- 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 +32 -68
- data/lib/active_record/nested_attributes.rb +24 -5
- data/lib/active_record/normalization.rb +3 -7
- data/lib/active_record/persistence.rb +30 -352
- data/lib/active_record/query_cache.rb +19 -8
- data/lib/active_record/query_logs.rb +15 -0
- data/lib/active_record/querying.rb +21 -9
- data/lib/active_record/railtie.rb +42 -57
- data/lib/active_record/railties/controller_runtime.rb +13 -4
- data/lib/active_record/railties/databases.rake +40 -43
- data/lib/active_record/reflection.rb +98 -36
- data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
- data/lib/active_record/relation/batches.rb +14 -8
- data/lib/active_record/relation/calculations.rb +96 -63
- 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/association_query_value.rb +9 -3
- data/lib/active_record/relation/predicate_builder.rb +3 -3
- data/lib/active_record/relation/query_methods.rb +224 -58
- 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/scoping/named.rb +1 -0
- data/lib/active_record/signed_id.rb +20 -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 +81 -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 +86 -89
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +2 -2
- data/lib/active_record/token_for.rb +22 -12
- data/lib/active_record/touch_later.rb +1 -1
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +70 -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 +15 -10
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +148 -39
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +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/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 -12
@@ -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#lease_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,105 @@ 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 WeakThreadKeyMap # :nodoc:
|
122
|
+
# FIXME: On 3.3 we could use ObjectSpace::WeakKeyMap
|
123
|
+
# but it currently cause GC crashes: https://github.com/byroot/rails/pull/3
|
124
|
+
def initialize
|
125
|
+
@map = {}
|
126
|
+
end
|
127
|
+
|
128
|
+
def clear
|
129
|
+
@map.clear
|
130
|
+
end
|
131
|
+
|
132
|
+
def [](key)
|
133
|
+
@map[key]
|
134
|
+
end
|
135
|
+
|
136
|
+
def []=(key, value)
|
137
|
+
@map.select! { |c, _| c.alive? }
|
138
|
+
@map[key] = value
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
class Lease # :nodoc:
|
143
|
+
attr_accessor :connection, :sticky
|
144
|
+
|
145
|
+
def initialize
|
146
|
+
@connection = nil
|
147
|
+
@sticky = nil
|
148
|
+
end
|
149
|
+
|
150
|
+
def release
|
151
|
+
conn = @connection
|
152
|
+
@connection = nil
|
153
|
+
@sticky = nil
|
154
|
+
conn
|
155
|
+
end
|
156
|
+
|
157
|
+
def clear(connection)
|
158
|
+
if @connection == connection
|
159
|
+
@connection = nil
|
160
|
+
@sticky = nil
|
161
|
+
true
|
162
|
+
else
|
163
|
+
false
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
class LeaseRegistry # :nodoc:
|
169
|
+
def initialize
|
170
|
+
@mutex = Mutex.new
|
171
|
+
@map = WeakThreadKeyMap.new
|
172
|
+
end
|
173
|
+
|
174
|
+
def [](context)
|
175
|
+
@mutex.synchronize do
|
176
|
+
@map[context] ||= Lease.new
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def clear
|
181
|
+
@mutex.synchronize do
|
182
|
+
@map.clear
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
module ExecutorHooks # :nodoc:
|
188
|
+
class << self
|
189
|
+
def run
|
190
|
+
# noop
|
191
|
+
end
|
192
|
+
|
193
|
+
def complete(_)
|
194
|
+
ActiveRecord::Base.connection_handler.each_connection_pool do |pool|
|
195
|
+
if (connection = pool.active_connection?)
|
196
|
+
transaction = connection.current_transaction
|
197
|
+
if transaction.closed? || !transaction.joinable?
|
198
|
+
pool.release_connection
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
class << self
|
207
|
+
def install_executor_hooks(executor = ActiveSupport::Executor)
|
208
|
+
executor.register_hook(ExecutorHooks)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
105
212
|
include MonitorMixin
|
106
|
-
|
213
|
+
prepend QueryCache::ConnectionPoolConfiguration
|
107
214
|
include ConnectionAdapters::AbstractPool
|
108
215
|
|
109
216
|
attr_accessor :automatic_reconnect, :checkout_timeout
|
110
217
|
attr_reader :db_config, :size, :reaper, :pool_config, :async_executor, :role, :shard
|
111
218
|
|
112
|
-
delegate :schema_reflection, :
|
219
|
+
delegate :schema_reflection, :server_version, to: :pool_config
|
113
220
|
|
114
221
|
# Creates a new ConnectionPool object. +pool_config+ is a PoolConfig
|
115
222
|
# object which describes database connection information (e.g. adapter,
|
@@ -137,9 +244,9 @@ module ActiveRecord
|
|
137
244
|
# then that +thread+ does indeed own that +conn+. However, an absence of such
|
138
245
|
# mapping does not mean that the +thread+ doesn't own the said connection. In
|
139
246
|
# that case +conn.owner+ attr should be consulted.
|
140
|
-
# Access and modification of <tt>@
|
247
|
+
# Access and modification of <tt>@leases</tt> does not require
|
141
248
|
# synchronization.
|
142
|
-
@
|
249
|
+
@leases = LeaseRegistry.new
|
143
250
|
|
144
251
|
@connections = []
|
145
252
|
@automatic_reconnect = true
|
@@ -152,86 +259,176 @@ module ActiveRecord
|
|
152
259
|
@threads_blocking_new_connections = 0
|
153
260
|
|
154
261
|
@available = ConnectionLeasingQueue.new self
|
155
|
-
|
156
|
-
@
|
262
|
+
@pinned_connection = nil
|
263
|
+
@pinned_connections_depth = 0
|
157
264
|
|
158
265
|
@async_executor = build_async_executor
|
159
266
|
|
267
|
+
@schema_cache = nil
|
268
|
+
|
160
269
|
@reaper = Reaper.new(self, db_config.reaping_frequency)
|
161
270
|
@reaper.run
|
162
271
|
end
|
163
272
|
|
164
|
-
def
|
165
|
-
|
166
|
-
|
167
|
-
else
|
168
|
-
@lock_thread = nil
|
169
|
-
end
|
273
|
+
def inspect # :nodoc:
|
274
|
+
name_field = " name=#{db_config.name.inspect}" unless db_config.name == "primary"
|
275
|
+
shard_field = " shard=#{@shard.inspect}" unless @shard == :default
|
170
276
|
|
171
|
-
|
172
|
-
|
173
|
-
|
277
|
+
"#<#{self.class.name} env_name=#{db_config.env_name.inspect}#{name_field} role=#{role.inspect}#{shard_field}>"
|
278
|
+
end
|
279
|
+
|
280
|
+
def schema_cache
|
281
|
+
@schema_cache ||= BoundSchemaReflection.new(schema_reflection, self)
|
282
|
+
end
|
283
|
+
|
284
|
+
def schema_reflection=(schema_reflection)
|
285
|
+
pool_config.schema_reflection = schema_reflection
|
286
|
+
@schema_cache = nil
|
287
|
+
end
|
288
|
+
|
289
|
+
def migration_context # :nodoc:
|
290
|
+
MigrationContext.new(migrations_paths, schema_migration, internal_metadata)
|
291
|
+
end
|
292
|
+
|
293
|
+
def migrations_paths # :nodoc:
|
294
|
+
db_config.migrations_paths || Migrator.migrations_paths
|
295
|
+
end
|
296
|
+
|
297
|
+
def schema_migration # :nodoc:
|
298
|
+
SchemaMigration.new(self)
|
299
|
+
end
|
300
|
+
|
301
|
+
def internal_metadata # :nodoc:
|
302
|
+
InternalMetadata.new(self)
|
174
303
|
end
|
175
304
|
|
176
305
|
# Retrieve the connection associated with the current thread, or call
|
177
306
|
# #checkout to obtain one if necessary.
|
178
307
|
#
|
179
|
-
# #
|
308
|
+
# #lease_connection can be called any number of times; the connection is
|
180
309
|
# held in a cache keyed by a thread.
|
310
|
+
def lease_connection
|
311
|
+
lease = connection_lease
|
312
|
+
lease.sticky = true
|
313
|
+
lease.connection ||= checkout
|
314
|
+
end
|
315
|
+
|
316
|
+
def permanent_lease? # :nodoc:
|
317
|
+
connection_lease.sticky.nil?
|
318
|
+
end
|
319
|
+
|
181
320
|
def connection
|
182
|
-
|
321
|
+
ActiveRecord.deprecator.warn(<<~MSG)
|
322
|
+
ActiveRecord::ConnectionAdapters::ConnectionPool#connection is deprecated
|
323
|
+
and will be removed in Rails 8.0. Use #lease_connection instead.
|
324
|
+
MSG
|
325
|
+
lease_connection
|
326
|
+
end
|
327
|
+
|
328
|
+
def pin_connection!(lock_thread) # :nodoc:
|
329
|
+
@pinned_connection ||= (connection_lease&.connection || checkout)
|
330
|
+
@pinned_connections_depth += 1
|
331
|
+
|
332
|
+
# Any leased connection must be in @connections otherwise
|
333
|
+
# some methods like #connected? won't behave correctly
|
334
|
+
unless @connections.include?(@pinned_connection)
|
335
|
+
@connections << @pinned_connection
|
336
|
+
end
|
337
|
+
|
338
|
+
@pinned_connection.lock_thread = ActiveSupport::IsolatedExecutionState.context if lock_thread
|
339
|
+
@pinned_connection.verify! # eagerly validate the connection
|
340
|
+
@pinned_connection.begin_transaction joinable: false, _lazy: false
|
341
|
+
end
|
342
|
+
|
343
|
+
def unpin_connection! # :nodoc:
|
344
|
+
raise "There isn't a pinned connection #{object_id}" unless @pinned_connection
|
345
|
+
|
346
|
+
clean = true
|
347
|
+
@pinned_connection.lock.synchronize do
|
348
|
+
@pinned_connections_depth -= 1
|
349
|
+
connection = @pinned_connection
|
350
|
+
@pinned_connection = nil if @pinned_connections_depth.zero?
|
351
|
+
|
352
|
+
if connection.transaction_open?
|
353
|
+
connection.rollback_transaction
|
354
|
+
else
|
355
|
+
# Something committed or rolled back the transaction
|
356
|
+
clean = false
|
357
|
+
connection.reset!
|
358
|
+
end
|
359
|
+
|
360
|
+
if @pinned_connection.nil?
|
361
|
+
connection.steal!
|
362
|
+
connection.lock_thread = nil
|
363
|
+
checkin(connection)
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
clean
|
183
368
|
end
|
184
369
|
|
185
370
|
def connection_class # :nodoc:
|
186
371
|
pool_config.connection_class
|
187
372
|
end
|
188
|
-
alias :connection_klass :connection_class
|
189
|
-
deprecate :connection_klass, deprecator: ActiveRecord.deprecator
|
190
373
|
|
191
374
|
# Returns true if there is an open connection being used for the current thread.
|
192
375
|
#
|
193
376
|
# This method only works for connections that have been obtained through
|
194
|
-
# #
|
377
|
+
# #lease_connection or #with_connection methods. Connections obtained through
|
195
378
|
# #checkout will not be detected by #active_connection?
|
196
379
|
def active_connection?
|
197
|
-
|
380
|
+
connection_lease.connection
|
198
381
|
end
|
382
|
+
alias_method :active_connection, :active_connection? # :nodoc:
|
199
383
|
|
200
384
|
# Signal that the thread is finished with the current connection.
|
201
385
|
# #release_connection releases the connection-thread association
|
202
386
|
# and returns the connection to the pool.
|
203
387
|
#
|
204
388
|
# This method only works for connections that have been obtained through
|
205
|
-
# #
|
389
|
+
# #lease_connection or #with_connection methods, connections obtained through
|
206
390
|
# #checkout will not be automatically released.
|
207
|
-
def release_connection(
|
208
|
-
if conn =
|
391
|
+
def release_connection(existing_lease = nil)
|
392
|
+
if conn = connection_lease.release
|
209
393
|
checkin conn
|
394
|
+
return true
|
210
395
|
end
|
396
|
+
false
|
211
397
|
end
|
212
398
|
|
213
399
|
# Yields a connection from the connection pool to the block. If no connection
|
214
400
|
# is already checked out by the current thread, a connection will be checked
|
215
401
|
# out from the pool, yielded to the block, and then returned to the pool when
|
216
402
|
# the block is finished. If a connection has already been checked out on the
|
217
|
-
# current thread, such as via #
|
403
|
+
# current thread, such as via #lease_connection or #with_connection, that existing
|
218
404
|
# connection will be the one yielded and it will not be returned to the pool
|
219
405
|
# automatically at the end of the block; it is expected that such an existing
|
220
406
|
# connection will be properly returned to the pool by the code that checked
|
221
407
|
# it out.
|
222
|
-
def with_connection
|
223
|
-
|
224
|
-
|
225
|
-
|
408
|
+
def with_connection(prevent_permanent_checkout: false)
|
409
|
+
lease = connection_lease
|
410
|
+
sticky_was = lease.sticky
|
411
|
+
lease.sticky = false if prevent_permanent_checkout
|
412
|
+
|
413
|
+
if lease.connection
|
414
|
+
begin
|
415
|
+
yield lease.connection
|
416
|
+
ensure
|
417
|
+
lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
|
418
|
+
end
|
419
|
+
else
|
420
|
+
begin
|
421
|
+
yield lease.connection = checkout
|
422
|
+
ensure
|
423
|
+
lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
|
424
|
+
release_connection(lease) unless lease.sticky
|
425
|
+
end
|
226
426
|
end
|
227
|
-
yield conn
|
228
|
-
ensure
|
229
|
-
release_connection if fresh_connection
|
230
427
|
end
|
231
428
|
|
232
429
|
# Returns true if a connection has already been opened.
|
233
430
|
def connected?
|
234
|
-
synchronize { @connections.any? }
|
431
|
+
synchronize { @connections.any?(&:connected?) }
|
235
432
|
end
|
236
433
|
|
237
434
|
# Returns an array containing the connections currently in the pool.
|
@@ -266,6 +463,7 @@ module ActiveRecord
|
|
266
463
|
conn.disconnect!
|
267
464
|
end
|
268
465
|
@connections = []
|
466
|
+
@leases.clear
|
269
467
|
@available.clear
|
270
468
|
end
|
271
469
|
end
|
@@ -292,7 +490,7 @@ module ActiveRecord
|
|
292
490
|
@connections.each do |conn|
|
293
491
|
conn.discard!
|
294
492
|
end
|
295
|
-
@connections = @available = @
|
493
|
+
@connections = @available = @leases = nil
|
296
494
|
end
|
297
495
|
end
|
298
496
|
|
@@ -350,9 +548,21 @@ module ActiveRecord
|
|
350
548
|
# Raises:
|
351
549
|
# - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool.
|
352
550
|
def checkout(checkout_timeout = @checkout_timeout)
|
353
|
-
|
354
|
-
|
355
|
-
|
551
|
+
if @pinned_connection
|
552
|
+
@pinned_connection.lock.synchronize do
|
553
|
+
synchronize do
|
554
|
+
@pinned_connection.verify!
|
555
|
+
# Any leased connection must be in @connections otherwise
|
556
|
+
# some methods like #connected? won't behave correctly
|
557
|
+
unless @connections.include?(@pinned_connection)
|
558
|
+
@connections << @pinned_connection
|
559
|
+
end
|
560
|
+
end
|
561
|
+
end
|
562
|
+
@pinned_connection
|
563
|
+
else
|
564
|
+
checkout_and_verify(acquire_connection(checkout_timeout))
|
565
|
+
end
|
356
566
|
end
|
357
567
|
|
358
568
|
# Check-in a database connection back into the pool, indicating that you
|
@@ -361,15 +571,16 @@ module ActiveRecord
|
|
361
571
|
# +conn+: an AbstractAdapter object, which was obtained by earlier by
|
362
572
|
# calling #checkout on this pool.
|
363
573
|
def checkin(conn)
|
574
|
+
return if @pinned_connection.equal?(conn)
|
575
|
+
|
364
576
|
conn.lock.synchronize do
|
365
577
|
synchronize do
|
366
|
-
|
578
|
+
connection_lease.clear(conn)
|
367
579
|
|
368
580
|
conn._run_checkin_callbacks do
|
369
581
|
conn.expire
|
370
582
|
end
|
371
583
|
|
372
|
-
conn.lock_thread = nil
|
373
584
|
@available.add conn
|
374
585
|
end
|
375
586
|
end
|
@@ -486,6 +697,10 @@ module ActiveRecord
|
|
486
697
|
end
|
487
698
|
|
488
699
|
private
|
700
|
+
def connection_lease
|
701
|
+
@leases[ActiveSupport::IsolatedExecutionState.context]
|
702
|
+
end
|
703
|
+
|
489
704
|
def build_async_executor
|
490
705
|
case ActiveRecord.async_query_executor
|
491
706
|
when :multi_thread_pool
|
@@ -514,19 +729,6 @@ module ActiveRecord
|
|
514
729
|
end
|
515
730
|
end
|
516
731
|
|
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
732
|
# Take control of all existing connections so a "group" action such as
|
531
733
|
# reload/disconnect can be performed safely. It is no longer enough to
|
532
734
|
# wrap it in +synchronize+ because some pool's actions are allowed
|
@@ -540,6 +742,8 @@ module ActiveRecord
|
|
540
742
|
|
541
743
|
def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true)
|
542
744
|
collected_conns = synchronize do
|
745
|
+
reap # No need to wait for dead owners
|
746
|
+
|
543
747
|
# account for our own connections
|
544
748
|
@connections.select { |conn| conn.owner == ActiveSupport::IsolatedExecutionState.context }
|
545
749
|
end
|
@@ -551,6 +755,7 @@ module ActiveRecord
|
|
551
755
|
loop do
|
552
756
|
synchronize do
|
553
757
|
return if collected_conns.size == @connections.size && @now_connecting == 0
|
758
|
+
|
554
759
|
remaining_timeout = timeout_time - Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
555
760
|
remaining_timeout = 0 if remaining_timeout < 0
|
556
761
|
conn = checkout_for_exclusive_access(remaining_timeout)
|
@@ -601,7 +806,7 @@ module ActiveRecord
|
|
601
806
|
|
602
807
|
msg << " (#{thread_report.join(', ')})" if thread_report.any?
|
603
808
|
|
604
|
-
raise ExclusiveConnectionTimeoutError,
|
809
|
+
raise ExclusiveConnectionTimeoutError.new(msg, connection_pool: self)
|
605
810
|
end
|
606
811
|
|
607
812
|
def with_new_connections_blocked
|
@@ -663,26 +868,20 @@ module ActiveRecord
|
|
663
868
|
@available.poll(checkout_timeout)
|
664
869
|
end
|
665
870
|
end
|
871
|
+
rescue ConnectionTimeoutError => ex
|
872
|
+
raise ex.set_pool(self)
|
666
873
|
end
|
667
874
|
|
668
875
|
#--
|
669
876
|
# if owner_thread param is omitted, this must be called in synchronize block
|
670
877
|
def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
|
671
|
-
@
|
878
|
+
@leases[owner_thread].clear(conn)
|
672
879
|
end
|
673
880
|
alias_method :release, :remove_connection_from_thread_cache
|
674
881
|
|
675
882
|
def new_connection
|
676
|
-
connection =
|
883
|
+
connection = db_config.new_connection
|
677
884
|
connection.pool = self
|
678
|
-
|
679
|
-
begin
|
680
|
-
connection.check_version
|
681
|
-
rescue
|
682
|
-
connection.disconnect!
|
683
|
-
raise
|
684
|
-
end
|
685
|
-
|
686
885
|
connection
|
687
886
|
rescue ConnectionNotEstablished => ex
|
688
887
|
raise ex.set_pool(self)
|
@@ -723,6 +922,12 @@ module ActiveRecord
|
|
723
922
|
def adopt_connection(conn)
|
724
923
|
conn.pool = self
|
725
924
|
@connections << conn
|
925
|
+
|
926
|
+
# We just created the first connection, it's time to load the schema
|
927
|
+
# cache if that wasn't eagerly done before
|
928
|
+
if @schema_cache.nil? && ActiveRecord.lazily_load_schema_cache
|
929
|
+
schema_cache.load!
|
930
|
+
end
|
726
931
|
end
|
727
932
|
|
728
933
|
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.user_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
|