activerecord 7.1.3.2 → 7.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 +570 -2094
- 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 +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 +11 -5
- 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 +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 +6 -8
- 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 -273
- 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 +2 -2
- 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 -6
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +89 -58
- data/lib/active_record/attributes.rb +60 -45
- data/lib/active_record/autosave_association.rb +17 -31
- 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 +244 -58
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +35 -18
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +188 -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 +125 -62
- data/lib/active_record/connection_adapters/abstract_adapter.rb +38 -59
- 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 +8 -1
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +16 -15
- 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 +127 -77
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +15 -15
- 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 +60 -39
- 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 +19 -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 +3 -3
- data/lib/active_record/encryption/encrypted_attribute_type.rb +26 -6
- data/lib/active_record/encryption/encryptor.rb +18 -3
- 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 +26 -6
- 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 +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 +39 -70
- 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 +54 -67
- data/lib/active_record/railties/controller_runtime.rb +13 -4
- data/lib/active_record/railties/databases.rake +42 -45
- data/lib/active_record/reflection.rb +102 -37
- 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 +95 -62
- 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/polymorphic_array_value.rb +6 -1
- data/lib/active_record/relation/predicate_builder.rb +3 -3
- data/lib/active_record/relation/query_methods.rb +212 -47
- 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/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 +87 -48
- 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 +87 -89
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +5 -3
- 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 +14 -10
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +150 -41
- 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
- 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,80 @@ 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
|
+
|
105
187
|
include MonitorMixin
|
106
|
-
|
188
|
+
prepend QueryCache::ConnectionPoolConfiguration
|
107
189
|
include ConnectionAdapters::AbstractPool
|
108
190
|
|
109
191
|
attr_accessor :automatic_reconnect, :checkout_timeout
|
110
192
|
attr_reader :db_config, :size, :reaper, :pool_config, :async_executor, :role, :shard
|
111
193
|
|
112
|
-
delegate :schema_reflection, :
|
194
|
+
delegate :schema_reflection, :server_version, to: :pool_config
|
113
195
|
|
114
196
|
# Creates a new ConnectionPool object. +pool_config+ is a PoolConfig
|
115
197
|
# object which describes database connection information (e.g. adapter,
|
@@ -137,9 +219,9 @@ module ActiveRecord
|
|
137
219
|
# then that +thread+ does indeed own that +conn+. However, an absence of such
|
138
220
|
# mapping does not mean that the +thread+ doesn't own the said connection. In
|
139
221
|
# that case +conn.owner+ attr should be consulted.
|
140
|
-
# Access and modification of <tt>@
|
222
|
+
# Access and modification of <tt>@leases</tt> does not require
|
141
223
|
# synchronization.
|
142
|
-
@
|
224
|
+
@leases = LeaseRegistry.new
|
143
225
|
|
144
226
|
@connections = []
|
145
227
|
@automatic_reconnect = true
|
@@ -152,86 +234,175 @@ module ActiveRecord
|
|
152
234
|
@threads_blocking_new_connections = 0
|
153
235
|
|
154
236
|
@available = ConnectionLeasingQueue.new self
|
155
|
-
|
156
|
-
@
|
237
|
+
@pinned_connection = nil
|
238
|
+
@pinned_connections_depth = 0
|
157
239
|
|
158
240
|
@async_executor = build_async_executor
|
159
241
|
|
242
|
+
@schema_cache = nil
|
243
|
+
|
160
244
|
@reaper = Reaper.new(self, db_config.reaping_frequency)
|
161
245
|
@reaper.run
|
162
246
|
end
|
163
247
|
|
164
|
-
def
|
165
|
-
|
166
|
-
|
167
|
-
else
|
168
|
-
@lock_thread = nil
|
169
|
-
end
|
248
|
+
def inspect # :nodoc:
|
249
|
+
name_field = " name=#{db_config.name.inspect}" unless db_config.name == "primary"
|
250
|
+
shard_field = " shard=#{@shard.inspect}" unless @shard == :default
|
170
251
|
|
171
|
-
|
172
|
-
|
173
|
-
|
252
|
+
"#<#{self.class.name} env_name=#{db_config.env_name.inspect}#{name_field} role=#{role.inspect}#{shard_field}>"
|
253
|
+
end
|
254
|
+
|
255
|
+
def schema_cache
|
256
|
+
@schema_cache ||= BoundSchemaReflection.new(schema_reflection, self)
|
257
|
+
end
|
258
|
+
|
259
|
+
def schema_reflection=(schema_reflection)
|
260
|
+
pool_config.schema_reflection = schema_reflection
|
261
|
+
@schema_cache = nil
|
262
|
+
end
|
263
|
+
|
264
|
+
def migration_context # :nodoc:
|
265
|
+
MigrationContext.new(migrations_paths, schema_migration, internal_metadata)
|
266
|
+
end
|
267
|
+
|
268
|
+
def migrations_paths # :nodoc:
|
269
|
+
db_config.migrations_paths || Migrator.migrations_paths
|
270
|
+
end
|
271
|
+
|
272
|
+
def schema_migration # :nodoc:
|
273
|
+
SchemaMigration.new(self)
|
274
|
+
end
|
275
|
+
|
276
|
+
def internal_metadata # :nodoc:
|
277
|
+
InternalMetadata.new(self)
|
174
278
|
end
|
175
279
|
|
176
280
|
# Retrieve the connection associated with the current thread, or call
|
177
281
|
# #checkout to obtain one if necessary.
|
178
282
|
#
|
179
|
-
# #
|
283
|
+
# #lease_connection can be called any number of times; the connection is
|
180
284
|
# held in a cache keyed by a thread.
|
285
|
+
def lease_connection
|
286
|
+
lease = connection_lease
|
287
|
+
lease.sticky = true
|
288
|
+
lease.connection ||= checkout
|
289
|
+
end
|
290
|
+
|
291
|
+
def permanent_lease? # :nodoc:
|
292
|
+
connection_lease.sticky.nil?
|
293
|
+
end
|
294
|
+
|
181
295
|
def connection
|
182
|
-
|
296
|
+
ActiveRecord.deprecator.warn(<<~MSG)
|
297
|
+
ActiveRecord::ConnectionAdapters::ConnectionPool#connection is deprecated
|
298
|
+
and will be removed in Rails 8.0. Use #lease_connection instead.
|
299
|
+
MSG
|
300
|
+
lease_connection
|
301
|
+
end
|
302
|
+
|
303
|
+
def pin_connection!(lock_thread) # :nodoc:
|
304
|
+
@pinned_connection ||= (connection_lease&.connection || checkout)
|
305
|
+
@pinned_connections_depth += 1
|
306
|
+
|
307
|
+
# Any leased connection must be in @connections otherwise
|
308
|
+
# some methods like #connected? won't behave correctly
|
309
|
+
unless @connections.include?(@pinned_connection)
|
310
|
+
@connections << @pinned_connection
|
311
|
+
end
|
312
|
+
|
313
|
+
@pinned_connection.lock_thread = ActiveSupport::IsolatedExecutionState.context if lock_thread
|
314
|
+
@pinned_connection.verify! # eagerly validate the connection
|
315
|
+
@pinned_connection.begin_transaction joinable: false, _lazy: false
|
316
|
+
end
|
317
|
+
|
318
|
+
def unpin_connection! # :nodoc:
|
319
|
+
raise "There isn't a pinned connection #{object_id}" unless @pinned_connection
|
320
|
+
|
321
|
+
clean = true
|
322
|
+
@pinned_connection.lock.synchronize do
|
323
|
+
@pinned_connections_depth -= 1
|
324
|
+
connection = @pinned_connection
|
325
|
+
@pinned_connection = nil if @pinned_connections_depth.zero?
|
326
|
+
|
327
|
+
if connection.transaction_open?
|
328
|
+
connection.rollback_transaction
|
329
|
+
else
|
330
|
+
# Something committed or rolled back the transaction
|
331
|
+
clean = false
|
332
|
+
connection.reset!
|
333
|
+
end
|
334
|
+
|
335
|
+
if @pinned_connection.nil?
|
336
|
+
connection.lock_thread = nil
|
337
|
+
checkin(connection)
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
clean
|
183
342
|
end
|
184
343
|
|
185
344
|
def connection_class # :nodoc:
|
186
345
|
pool_config.connection_class
|
187
346
|
end
|
188
|
-
alias :connection_klass :connection_class
|
189
|
-
deprecate :connection_klass, deprecator: ActiveRecord.deprecator
|
190
347
|
|
191
348
|
# Returns true if there is an open connection being used for the current thread.
|
192
349
|
#
|
193
350
|
# This method only works for connections that have been obtained through
|
194
|
-
# #
|
351
|
+
# #lease_connection or #with_connection methods. Connections obtained through
|
195
352
|
# #checkout will not be detected by #active_connection?
|
196
353
|
def active_connection?
|
197
|
-
|
354
|
+
connection_lease.connection
|
198
355
|
end
|
356
|
+
alias_method :active_connection, :active_connection? # :nodoc:
|
199
357
|
|
200
358
|
# Signal that the thread is finished with the current connection.
|
201
359
|
# #release_connection releases the connection-thread association
|
202
360
|
# and returns the connection to the pool.
|
203
361
|
#
|
204
362
|
# This method only works for connections that have been obtained through
|
205
|
-
# #
|
363
|
+
# #lease_connection or #with_connection methods, connections obtained through
|
206
364
|
# #checkout will not be automatically released.
|
207
|
-
def release_connection(
|
208
|
-
if conn =
|
365
|
+
def release_connection(existing_lease = nil)
|
366
|
+
if conn = connection_lease.release
|
209
367
|
checkin conn
|
368
|
+
return true
|
210
369
|
end
|
370
|
+
false
|
211
371
|
end
|
212
372
|
|
213
373
|
# Yields a connection from the connection pool to the block. If no connection
|
214
374
|
# is already checked out by the current thread, a connection will be checked
|
215
375
|
# out from the pool, yielded to the block, and then returned to the pool when
|
216
376
|
# the block is finished. If a connection has already been checked out on the
|
217
|
-
# current thread, such as via #
|
377
|
+
# current thread, such as via #lease_connection or #with_connection, that existing
|
218
378
|
# connection will be the one yielded and it will not be returned to the pool
|
219
379
|
# automatically at the end of the block; it is expected that such an existing
|
220
380
|
# connection will be properly returned to the pool by the code that checked
|
221
381
|
# it out.
|
222
|
-
def with_connection
|
223
|
-
|
224
|
-
|
225
|
-
|
382
|
+
def with_connection(prevent_permanent_checkout: false)
|
383
|
+
lease = connection_lease
|
384
|
+
sticky_was = lease.sticky
|
385
|
+
lease.sticky = false if prevent_permanent_checkout
|
386
|
+
|
387
|
+
if lease.connection
|
388
|
+
begin
|
389
|
+
yield lease.connection
|
390
|
+
ensure
|
391
|
+
lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
|
392
|
+
end
|
393
|
+
else
|
394
|
+
begin
|
395
|
+
yield lease.connection = checkout
|
396
|
+
ensure
|
397
|
+
lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
|
398
|
+
release_connection(lease) unless lease.sticky
|
399
|
+
end
|
226
400
|
end
|
227
|
-
yield conn
|
228
|
-
ensure
|
229
|
-
release_connection if fresh_connection
|
230
401
|
end
|
231
402
|
|
232
403
|
# Returns true if a connection has already been opened.
|
233
404
|
def connected?
|
234
|
-
synchronize { @connections.any? }
|
405
|
+
synchronize { @connections.any?(&:connected?) }
|
235
406
|
end
|
236
407
|
|
237
408
|
# Returns an array containing the connections currently in the pool.
|
@@ -266,6 +437,7 @@ module ActiveRecord
|
|
266
437
|
conn.disconnect!
|
267
438
|
end
|
268
439
|
@connections = []
|
440
|
+
@leases.clear
|
269
441
|
@available.clear
|
270
442
|
end
|
271
443
|
end
|
@@ -292,7 +464,7 @@ module ActiveRecord
|
|
292
464
|
@connections.each do |conn|
|
293
465
|
conn.discard!
|
294
466
|
end
|
295
|
-
@connections = @available = @
|
467
|
+
@connections = @available = @leases = nil
|
296
468
|
end
|
297
469
|
end
|
298
470
|
|
@@ -350,9 +522,21 @@ module ActiveRecord
|
|
350
522
|
# Raises:
|
351
523
|
# - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool.
|
352
524
|
def checkout(checkout_timeout = @checkout_timeout)
|
353
|
-
|
354
|
-
|
355
|
-
|
525
|
+
if @pinned_connection
|
526
|
+
@pinned_connection.lock.synchronize do
|
527
|
+
synchronize do
|
528
|
+
@pinned_connection.verify!
|
529
|
+
# Any leased connection must be in @connections otherwise
|
530
|
+
# some methods like #connected? won't behave correctly
|
531
|
+
unless @connections.include?(@pinned_connection)
|
532
|
+
@connections << @pinned_connection
|
533
|
+
end
|
534
|
+
end
|
535
|
+
end
|
536
|
+
@pinned_connection
|
537
|
+
else
|
538
|
+
checkout_and_verify(acquire_connection(checkout_timeout))
|
539
|
+
end
|
356
540
|
end
|
357
541
|
|
358
542
|
# Check-in a database connection back into the pool, indicating that you
|
@@ -361,15 +545,16 @@ module ActiveRecord
|
|
361
545
|
# +conn+: an AbstractAdapter object, which was obtained by earlier by
|
362
546
|
# calling #checkout on this pool.
|
363
547
|
def checkin(conn)
|
548
|
+
return if @pinned_connection.equal?(conn)
|
549
|
+
|
364
550
|
conn.lock.synchronize do
|
365
551
|
synchronize do
|
366
|
-
|
552
|
+
connection_lease.clear(conn)
|
367
553
|
|
368
554
|
conn._run_checkin_callbacks do
|
369
555
|
conn.expire
|
370
556
|
end
|
371
557
|
|
372
|
-
conn.lock_thread = nil
|
373
558
|
@available.add conn
|
374
559
|
end
|
375
560
|
end
|
@@ -486,6 +671,10 @@ module ActiveRecord
|
|
486
671
|
end
|
487
672
|
|
488
673
|
private
|
674
|
+
def connection_lease
|
675
|
+
@leases[ActiveSupport::IsolatedExecutionState.context]
|
676
|
+
end
|
677
|
+
|
489
678
|
def build_async_executor
|
490
679
|
case ActiveRecord.async_query_executor
|
491
680
|
when :multi_thread_pool
|
@@ -514,19 +703,6 @@ module ActiveRecord
|
|
514
703
|
end
|
515
704
|
end
|
516
705
|
|
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
706
|
# Take control of all existing connections so a "group" action such as
|
531
707
|
# reload/disconnect can be performed safely. It is no longer enough to
|
532
708
|
# wrap it in +synchronize+ because some pool's actions are allowed
|
@@ -540,6 +716,8 @@ module ActiveRecord
|
|
540
716
|
|
541
717
|
def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true)
|
542
718
|
collected_conns = synchronize do
|
719
|
+
reap # No need to wait for dead owners
|
720
|
+
|
543
721
|
# account for our own connections
|
544
722
|
@connections.select { |conn| conn.owner == ActiveSupport::IsolatedExecutionState.context }
|
545
723
|
end
|
@@ -551,6 +729,7 @@ module ActiveRecord
|
|
551
729
|
loop do
|
552
730
|
synchronize do
|
553
731
|
return if collected_conns.size == @connections.size && @now_connecting == 0
|
732
|
+
|
554
733
|
remaining_timeout = timeout_time - Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
555
734
|
remaining_timeout = 0 if remaining_timeout < 0
|
556
735
|
conn = checkout_for_exclusive_access(remaining_timeout)
|
@@ -601,7 +780,7 @@ module ActiveRecord
|
|
601
780
|
|
602
781
|
msg << " (#{thread_report.join(', ')})" if thread_report.any?
|
603
782
|
|
604
|
-
raise ExclusiveConnectionTimeoutError,
|
783
|
+
raise ExclusiveConnectionTimeoutError.new(msg, connection_pool: self)
|
605
784
|
end
|
606
785
|
|
607
786
|
def with_new_connections_blocked
|
@@ -663,19 +842,20 @@ module ActiveRecord
|
|
663
842
|
@available.poll(checkout_timeout)
|
664
843
|
end
|
665
844
|
end
|
845
|
+
rescue ConnectionTimeoutError => ex
|
846
|
+
raise ex.set_pool(self)
|
666
847
|
end
|
667
848
|
|
668
849
|
#--
|
669
850
|
# if owner_thread param is omitted, this must be called in synchronize block
|
670
851
|
def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
|
671
|
-
@
|
852
|
+
@leases[owner_thread].clear(conn)
|
672
853
|
end
|
673
854
|
alias_method :release, :remove_connection_from_thread_cache
|
674
855
|
|
675
856
|
def new_connection
|
676
|
-
connection =
|
857
|
+
connection = db_config.new_connection
|
677
858
|
connection.pool = self
|
678
|
-
connection.check_version
|
679
859
|
connection
|
680
860
|
rescue ConnectionNotEstablished => ex
|
681
861
|
raise ex.set_pool(self)
|
@@ -716,6 +896,12 @@ module ActiveRecord
|
|
716
896
|
def adopt_connection(conn)
|
717
897
|
conn.pool = self
|
718
898
|
@connections << conn
|
899
|
+
|
900
|
+
# We just created the first connection, it's time to load the schema
|
901
|
+
# cache if that wasn't eagerly done before
|
902
|
+
if @schema_cache.nil? && ActiveRecord.lazily_load_schema_cache
|
903
|
+
schema_cache.load!
|
904
|
+
end
|
719
905
|
end
|
720
906
|
|
721
907
|
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,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
|