activerecord 7.2.2.1 → 7.2.3
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 +324 -7
- data/README.rdoc +1 -1
- data/lib/active_record/associations/alias_tracker.rb +6 -4
- data/lib/active_record/associations/belongs_to_association.rb +18 -2
- data/lib/active_record/associations/collection_association.rb +9 -7
- data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
- data/lib/active_record/attribute_methods/serialization.rb +1 -1
- data/lib/active_record/attribute_methods.rb +24 -19
- data/lib/active_record/attributes.rb +37 -26
- data/lib/active_record/autosave_association.rb +22 -12
- data/lib/active_record/base.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +49 -32
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +17 -6
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +5 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +3 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +32 -3
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +14 -6
- data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -1
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +3 -3
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +8 -1
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +14 -12
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +8 -3
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +1 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +10 -5
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -1
- data/lib/active_record/connection_handling.rb +12 -8
- data/lib/active_record/core.rb +28 -8
- data/lib/active_record/counter_cache.rb +1 -1
- data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
- data/lib/active_record/delegated_type.rb +18 -18
- data/lib/active_record/encryption/encryptable_record.rb +1 -1
- data/lib/active_record/encryption/encrypted_attribute_type.rb +1 -1
- data/lib/active_record/encryption/encryptor.rb +21 -20
- data/lib/active_record/enum.rb +13 -12
- data/lib/active_record/errors.rb +3 -3
- data/lib/active_record/fixture_set/table_row.rb +19 -2
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/migration.rb +2 -1
- data/lib/active_record/query_logs.rb +4 -0
- data/lib/active_record/querying.rb +4 -4
- data/lib/active_record/railtie.rb +2 -2
- data/lib/active_record/railties/databases.rake +2 -1
- data/lib/active_record/relation/calculations.rb +35 -30
- data/lib/active_record/relation/finder_methods.rb +14 -13
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +2 -0
- data/lib/active_record/relation/query_attribute.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +16 -9
- data/lib/active_record/relation/where_clause.rb +8 -2
- data/lib/active_record/relation.rb +15 -5
- data/lib/active_record/schema_dumper.rb +29 -11
- data/lib/active_record/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +7 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +7 -0
- data/lib/active_record/transactions.rb +3 -1
- data/lib/active_record.rb +1 -1
- data/lib/arel/collectors/bind.rb +1 -1
- data/lib/arel/crud.rb +2 -0
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/delete_statement.rb +4 -2
- data/lib/arel/nodes/update_statement.rb +4 -2
- data/lib/arel/select_manager.rb +6 -2
- data/lib/arel/update_manager.rb +5 -0
- data/lib/arel/visitors/dot.rb +2 -0
- data/lib/arel/visitors/to_sql.rb +3 -1
- metadata +10 -13
|
@@ -21,28 +21,34 @@ module ActiveRecord
|
|
|
21
21
|
# your domain objects across much of Active Record, without having to
|
|
22
22
|
# rely on implementation details or monkey patching.
|
|
23
23
|
#
|
|
24
|
-
#
|
|
25
|
-
# column which this will persist to.
|
|
24
|
+
# ==== Parameters
|
|
26
25
|
#
|
|
27
|
-
# +
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
# Otherwise, the type will be ActiveModel::Type::Value.
|
|
31
|
-
# See the examples below for more information about providing custom type objects.
|
|
26
|
+
# [+name+]
|
|
27
|
+
# The name of the methods to define attribute methods for, and the
|
|
28
|
+
# column which this will persist to.
|
|
32
29
|
#
|
|
33
|
-
#
|
|
30
|
+
# [+cast_type+]
|
|
31
|
+
# A symbol such as +:string+ or +:integer+, or a type object to be used
|
|
32
|
+
# for this attribute. If this parameter is not passed, the previously
|
|
33
|
+
# defined type (if any) will be used. Otherwise, the type will be
|
|
34
|
+
# ActiveModel::Type::Value. See the examples below for more information
|
|
35
|
+
# about providing custom type objects.
|
|
34
36
|
#
|
|
35
|
-
#
|
|
37
|
+
# ==== Options
|
|
36
38
|
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
39
|
+
# [+:default+]
|
|
40
|
+
# The default value to use when no value is provided. If this option is
|
|
41
|
+
# not passed, the previously defined default value (if any) on the
|
|
42
|
+
# superclass or in the schema will be used. Otherwise, the default will
|
|
43
|
+
# be +nil+.
|
|
40
44
|
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
45
|
+
# [+:array+]
|
|
46
|
+
# (PostgreSQL only) Specifies that the type should be an array. See the
|
|
47
|
+
# examples below.
|
|
43
48
|
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
49
|
+
# [+:range+]
|
|
50
|
+
# (PostgreSQL only) Specifies that the type should be a range. See the
|
|
51
|
+
# examples below.
|
|
46
52
|
#
|
|
47
53
|
# When using a symbol for +cast_type+, extra options are forwarded to the
|
|
48
54
|
# constructor of the type object.
|
|
@@ -178,8 +184,8 @@ module ActiveRecord
|
|
|
178
184
|
# @currency_converter = currency_converter
|
|
179
185
|
# end
|
|
180
186
|
#
|
|
181
|
-
# # value will be the result of
|
|
182
|
-
# #
|
|
187
|
+
# # value will be the result of #deserialize or
|
|
188
|
+
# # #cast. Assumed to be an instance of Money in
|
|
183
189
|
# # this case.
|
|
184
190
|
# def serialize(value)
|
|
185
191
|
# value_in_bitcoins = @currency_converter.convert_to_bitcoins(value)
|
|
@@ -217,17 +223,22 @@ module ActiveRecord
|
|
|
217
223
|
# is provided so it can be used by plugin authors, application code
|
|
218
224
|
# should probably use ClassMethods#attribute.
|
|
219
225
|
#
|
|
220
|
-
#
|
|
226
|
+
# ==== Parameters
|
|
227
|
+
#
|
|
228
|
+
# [+name+]
|
|
229
|
+
# The name of the attribute being defined. Expected to be a +String+.
|
|
221
230
|
#
|
|
222
|
-
# +cast_type+
|
|
231
|
+
# [+cast_type+]
|
|
232
|
+
# The type object to use for this attribute.
|
|
223
233
|
#
|
|
224
|
-
# +default+
|
|
225
|
-
#
|
|
226
|
-
#
|
|
227
|
-
# will be
|
|
234
|
+
# [+default+]
|
|
235
|
+
# The default value to use when no value is provided. If this option
|
|
236
|
+
# is not passed, the previous default value (if any) will be used.
|
|
237
|
+
# Otherwise, the default will be +nil+. A proc can also be passed, and
|
|
238
|
+
# will be called once each time a new value is needed.
|
|
228
239
|
#
|
|
229
|
-
# +user_provided_default+
|
|
230
|
-
# +cast+ or +deserialize+.
|
|
240
|
+
# [+user_provided_default+]
|
|
241
|
+
# Whether the default value should be cast using +cast+ or +deserialize+.
|
|
231
242
|
def define_attribute(
|
|
232
243
|
name,
|
|
233
244
|
cast_type,
|
|
@@ -338,19 +338,29 @@ module ActiveRecord
|
|
|
338
338
|
return true if record.destroyed? || (association.options[:autosave] && record.marked_for_destruction?)
|
|
339
339
|
|
|
340
340
|
context = validation_context if custom_validation_context?
|
|
341
|
+
return true if record.valid?(context)
|
|
341
342
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
}
|
|
349
|
-
else
|
|
350
|
-
errors.add(association.reflection.name)
|
|
351
|
-
end
|
|
343
|
+
if context || record.changed_for_autosave?
|
|
344
|
+
associated_errors = record.errors.objects
|
|
345
|
+
else
|
|
346
|
+
# If there are existing invalid records in the DB, we should still be able to reference them.
|
|
347
|
+
# Unless a record (no matter where in the association chain) is invalid and is being changed.
|
|
348
|
+
associated_errors = record.errors.objects.select { |error| error.is_a?(Associations::NestedError) }
|
|
352
349
|
end
|
|
353
|
-
|
|
350
|
+
|
|
351
|
+
if association.options[:autosave]
|
|
352
|
+
return if equal?(record)
|
|
353
|
+
|
|
354
|
+
associated_errors.each { |error|
|
|
355
|
+
errors.objects.append(
|
|
356
|
+
Associations::NestedError.new(association, error)
|
|
357
|
+
)
|
|
358
|
+
}
|
|
359
|
+
elsif associated_errors.any?
|
|
360
|
+
errors.add(association.reflection.name)
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
errors.any?
|
|
354
364
|
end
|
|
355
365
|
|
|
356
366
|
# Is used as an around_save callback to check while saving a collection
|
|
@@ -483,7 +493,7 @@ module ActiveRecord
|
|
|
483
493
|
|
|
484
494
|
class_name = record._read_attribute(reflection.inverse_of.foreign_type)
|
|
485
495
|
|
|
486
|
-
reflection.active_record !=
|
|
496
|
+
reflection.active_record.polymorphic_name != class_name
|
|
487
497
|
end
|
|
488
498
|
|
|
489
499
|
# Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
|
data/lib/active_record/base.rb
CHANGED
|
@@ -256,13 +256,13 @@ module ActiveRecord # :nodoc:
|
|
|
256
256
|
# * AssociationTypeMismatch - The object assigned to the association wasn't of the type
|
|
257
257
|
# specified in the association definition.
|
|
258
258
|
# * AttributeAssignmentError - An error occurred while doing a mass assignment through the
|
|
259
|
-
# {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] method.
|
|
259
|
+
# {ActiveRecord::Base#attributes=}[rdoc-ref:ActiveModel::AttributeAssignment#attributes=] method.
|
|
260
260
|
# You can inspect the +attribute+ property of the exception object to determine which attribute
|
|
261
261
|
# triggered the error.
|
|
262
262
|
# * ConnectionNotEstablished - No connection has been established.
|
|
263
263
|
# Use {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection] before querying.
|
|
264
264
|
# * MultiparameterAssignmentErrors - Collection of errors that occurred during a mass assignment using the
|
|
265
|
-
# {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] method.
|
|
265
|
+
# {ActiveRecord::Base#attributes=}[rdoc-ref:ActiveModel::AttributeAssignment#attributes=] method.
|
|
266
266
|
# The +errors+ property of this exception contains an array of
|
|
267
267
|
# AttributeAssignmentError
|
|
268
268
|
# objects that should be inspected to determine which attributes triggered the errors.
|
|
@@ -38,6 +38,7 @@ module ActiveRecord
|
|
|
38
38
|
|
|
39
39
|
def schema_cache; end
|
|
40
40
|
def connection_class; end
|
|
41
|
+
def query_cache; end
|
|
41
42
|
def checkin(_); end
|
|
42
43
|
def remove(_); end
|
|
43
44
|
def async_executor; end
|
|
@@ -118,24 +119,30 @@ module ActiveRecord
|
|
|
118
119
|
# * private methods that require being called in a +synchronize+ blocks
|
|
119
120
|
# are now explicitly documented
|
|
120
121
|
class ConnectionPool
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
122
|
+
# Prior to 3.3.5, WeakKeyMap had a use after free bug
|
|
123
|
+
# https://bugs.ruby-lang.org/issues/20688
|
|
124
|
+
if ObjectSpace.const_defined?(:WeakKeyMap) && RUBY_VERSION >= "3.3.5"
|
|
125
|
+
WeakThreadKeyMap = ObjectSpace::WeakKeyMap
|
|
126
|
+
else
|
|
127
|
+
class WeakThreadKeyMap # :nodoc:
|
|
128
|
+
# FIXME: On 3.3 we could use ObjectSpace::WeakKeyMap
|
|
129
|
+
# but it currently cause GC crashes: https://github.com/byroot/rails/pull/3
|
|
130
|
+
def initialize
|
|
131
|
+
@map = {}
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def clear
|
|
135
|
+
@map.clear
|
|
136
|
+
end
|
|
131
137
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
138
|
+
def [](key)
|
|
139
|
+
@map[key]
|
|
140
|
+
end
|
|
135
141
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
142
|
+
def []=(key, value)
|
|
143
|
+
@map.select! { |c, _| c&.alive? }
|
|
144
|
+
@map[key] = value
|
|
145
|
+
end
|
|
139
146
|
end
|
|
140
147
|
end
|
|
141
148
|
|
|
@@ -309,8 +316,9 @@ module ActiveRecord
|
|
|
309
316
|
# held in a cache keyed by a thread.
|
|
310
317
|
def lease_connection
|
|
311
318
|
lease = connection_lease
|
|
312
|
-
lease.sticky = true
|
|
313
319
|
lease.connection ||= checkout
|
|
320
|
+
lease.sticky = true
|
|
321
|
+
lease.connection
|
|
314
322
|
end
|
|
315
323
|
|
|
316
324
|
def permanent_lease? # :nodoc:
|
|
@@ -336,6 +344,7 @@ module ActiveRecord
|
|
|
336
344
|
end
|
|
337
345
|
|
|
338
346
|
@pinned_connection.lock_thread = ActiveSupport::IsolatedExecutionState.context if lock_thread
|
|
347
|
+
@pinned_connection.pinned = true
|
|
339
348
|
@pinned_connection.verify! # eagerly validate the connection
|
|
340
349
|
@pinned_connection.begin_transaction joinable: false, _lazy: false
|
|
341
350
|
end
|
|
@@ -358,6 +367,7 @@ module ActiveRecord
|
|
|
358
367
|
end
|
|
359
368
|
|
|
360
369
|
if @pinned_connection.nil?
|
|
370
|
+
connection.pinned = false
|
|
361
371
|
connection.steal!
|
|
362
372
|
connection.lock_thread = nil
|
|
363
373
|
checkin(connection)
|
|
@@ -548,20 +558,25 @@ module ActiveRecord
|
|
|
548
558
|
# Raises:
|
|
549
559
|
# - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool.
|
|
550
560
|
def checkout(checkout_timeout = @checkout_timeout)
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
561
|
+
return checkout_and_verify(acquire_connection(checkout_timeout)) unless @pinned_connection
|
|
562
|
+
|
|
563
|
+
@pinned_connection.lock.synchronize do
|
|
564
|
+
synchronize do
|
|
565
|
+
# The pinned connection may have been cleaned up before we synchronized, so check if it is still present
|
|
566
|
+
if @pinned_connection
|
|
554
567
|
@pinned_connection.verify!
|
|
568
|
+
|
|
555
569
|
# Any leased connection must be in @connections otherwise
|
|
556
570
|
# some methods like #connected? won't behave correctly
|
|
557
571
|
unless @connections.include?(@pinned_connection)
|
|
558
572
|
@connections << @pinned_connection
|
|
559
573
|
end
|
|
574
|
+
|
|
575
|
+
@pinned_connection
|
|
576
|
+
else
|
|
577
|
+
checkout_and_verify(acquire_connection(checkout_timeout))
|
|
560
578
|
end
|
|
561
579
|
end
|
|
562
|
-
@pinned_connection
|
|
563
|
-
else
|
|
564
|
-
checkout_and_verify(acquire_connection(checkout_timeout))
|
|
565
580
|
end
|
|
566
581
|
end
|
|
567
582
|
|
|
@@ -696,6 +711,14 @@ module ActiveRecord
|
|
|
696
711
|
Thread.pass
|
|
697
712
|
end
|
|
698
713
|
|
|
714
|
+
def new_connection # :nodoc:
|
|
715
|
+
connection = db_config.new_connection
|
|
716
|
+
connection.pool = self
|
|
717
|
+
connection
|
|
718
|
+
rescue ConnectionNotEstablished => ex
|
|
719
|
+
raise ex.set_pool(self)
|
|
720
|
+
end
|
|
721
|
+
|
|
699
722
|
private
|
|
700
723
|
def connection_lease
|
|
701
724
|
@leases[ActiveSupport::IsolatedExecutionState.context]
|
|
@@ -875,18 +898,12 @@ module ActiveRecord
|
|
|
875
898
|
#--
|
|
876
899
|
# if owner_thread param is omitted, this must be called in synchronize block
|
|
877
900
|
def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
|
|
878
|
-
|
|
901
|
+
if owner_thread
|
|
902
|
+
@leases[owner_thread].clear(conn)
|
|
903
|
+
end
|
|
879
904
|
end
|
|
880
905
|
alias_method :release, :remove_connection_from_thread_cache
|
|
881
906
|
|
|
882
|
-
def new_connection
|
|
883
|
-
connection = db_config.new_connection
|
|
884
|
-
connection.pool = self
|
|
885
|
-
connection
|
|
886
|
-
rescue ConnectionNotEstablished => ex
|
|
887
|
-
raise ex.set_pool(self)
|
|
888
|
-
end
|
|
889
|
-
|
|
890
907
|
# If the pool is not at a <tt>@size</tt> limit, establish new connection. Connecting
|
|
891
908
|
# to the DB is done outside main synchronized section.
|
|
892
909
|
#--
|
|
@@ -191,15 +191,26 @@ module ActiveRecord
|
|
|
191
191
|
end
|
|
192
192
|
end
|
|
193
193
|
|
|
194
|
-
attr_accessor :query_cache
|
|
195
|
-
|
|
196
194
|
def initialize(*)
|
|
197
195
|
super
|
|
198
196
|
@query_cache = nil
|
|
199
197
|
end
|
|
200
198
|
|
|
199
|
+
attr_writer :query_cache
|
|
200
|
+
|
|
201
|
+
def query_cache
|
|
202
|
+
if @pinned && @owner != ActiveSupport::IsolatedExecutionState.context
|
|
203
|
+
# With transactional tests, if the connection is pinned, any thread
|
|
204
|
+
# other than the one that pinned the connection need to go through the
|
|
205
|
+
# query cache pool, so each thread get a different cache.
|
|
206
|
+
pool.query_cache
|
|
207
|
+
else
|
|
208
|
+
@query_cache
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
201
212
|
def query_cache_enabled
|
|
202
|
-
|
|
213
|
+
query_cache&.enabled?
|
|
203
214
|
end
|
|
204
215
|
|
|
205
216
|
# Enable the query cache within the block.
|
|
@@ -238,7 +249,7 @@ module ActiveRecord
|
|
|
238
249
|
|
|
239
250
|
# If arel is locked this is a SELECT ... FOR UPDATE or somesuch.
|
|
240
251
|
# Such queries should not be cached.
|
|
241
|
-
if
|
|
252
|
+
if query_cache_enabled && !(arel.respond_to?(:locked) && arel.locked)
|
|
242
253
|
sql, binds, preparable, allow_retry = to_sql_and_binds(arel, binds, preparable)
|
|
243
254
|
|
|
244
255
|
if async
|
|
@@ -262,7 +273,7 @@ module ActiveRecord
|
|
|
262
273
|
|
|
263
274
|
result = nil
|
|
264
275
|
@lock.synchronize do
|
|
265
|
-
result =
|
|
276
|
+
result = query_cache[key]
|
|
266
277
|
end
|
|
267
278
|
|
|
268
279
|
if result
|
|
@@ -281,7 +292,7 @@ module ActiveRecord
|
|
|
281
292
|
hit = true
|
|
282
293
|
|
|
283
294
|
@lock.synchronize do
|
|
284
|
-
result =
|
|
295
|
+
result = query_cache.compute_if_absent(key) do
|
|
285
296
|
hit = false
|
|
286
297
|
yield
|
|
287
298
|
end
|
|
@@ -160,6 +160,8 @@ module ActiveRecord
|
|
|
160
160
|
end
|
|
161
161
|
|
|
162
162
|
def defined_for?(to_table: nil, validate: nil, **options)
|
|
163
|
+
options = options.slice(*self.options.keys)
|
|
164
|
+
|
|
163
165
|
(to_table.nil? || to_table.to_s == self.to_table) &&
|
|
164
166
|
(validate.nil? || validate == self.options.fetch(:validate, validate)) &&
|
|
165
167
|
options.all? { |k, v| Array(self.options[k]).map(&:to_s) == Array(v).map(&:to_s) }
|
|
@@ -186,6 +188,8 @@ module ActiveRecord
|
|
|
186
188
|
end
|
|
187
189
|
|
|
188
190
|
def defined_for?(name:, expression: nil, validate: nil, **options)
|
|
191
|
+
options = options.slice(*self.options.keys)
|
|
192
|
+
|
|
189
193
|
self.name == name.to_s &&
|
|
190
194
|
(validate.nil? || validate == self.options.fetch(:validate, validate)) &&
|
|
191
195
|
options.all? { |k, v| self.options[k].to_s == v.to_s }
|
|
@@ -431,7 +435,7 @@ module ActiveRecord
|
|
|
431
435
|
#
|
|
432
436
|
# == Examples
|
|
433
437
|
#
|
|
434
|
-
# # Assuming
|
|
438
|
+
# # Assuming `td` is an instance of TableDefinition
|
|
435
439
|
# td.column(:granted, :boolean, index: true)
|
|
436
440
|
#
|
|
437
441
|
# == Short-hand examples
|
|
@@ -186,6 +186,9 @@ module ActiveRecord
|
|
|
186
186
|
# Join tables for {ActiveRecord::Base.has_and_belongs_to_many}[rdoc-ref:Associations::ClassMethods#has_and_belongs_to_many] should set it to false.
|
|
187
187
|
#
|
|
188
188
|
# A Symbol can be used to specify the type of the generated primary key column.
|
|
189
|
+
#
|
|
190
|
+
# A Hash can be used to specify the generated primary key column creation options.
|
|
191
|
+
# See {add_column}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_column] for available options.
|
|
189
192
|
# [<tt>:primary_key</tt>]
|
|
190
193
|
# The name of the primary key, if one is to be added automatically.
|
|
191
194
|
# Defaults to +id+. If <tt>:id</tt> is false, then this option is ignored.
|
|
@@ -43,6 +43,7 @@ module ActiveRecord
|
|
|
43
43
|
|
|
44
44
|
attr_reader :pool
|
|
45
45
|
attr_reader :visitor, :owner, :logger, :lock
|
|
46
|
+
attr_accessor :pinned # :nodoc:
|
|
46
47
|
alias :in_use? :owner
|
|
47
48
|
|
|
48
49
|
def pool=(value)
|
|
@@ -151,6 +152,7 @@ module ActiveRecord
|
|
|
151
152
|
end
|
|
152
153
|
|
|
153
154
|
@owner = nil
|
|
155
|
+
@pinned = false
|
|
154
156
|
@instrumenter = ActiveSupport::Notifications.instrumenter
|
|
155
157
|
@pool = ActiveRecord::ConnectionAdapters::NullPool.new
|
|
156
158
|
@idle_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
@@ -169,6 +171,7 @@ module ActiveRecord
|
|
|
169
171
|
@default_timezone = self.class.validate_default_timezone(@config[:default_timezone])
|
|
170
172
|
|
|
171
173
|
@raw_connection_dirty = false
|
|
174
|
+
@last_activity = nil
|
|
172
175
|
@verified = false
|
|
173
176
|
end
|
|
174
177
|
|
|
@@ -218,6 +221,10 @@ module ActiveRecord
|
|
|
218
221
|
(@config[:connection_retries] || 1).to_i
|
|
219
222
|
end
|
|
220
223
|
|
|
224
|
+
def verify_timeout
|
|
225
|
+
(@config[:verify_timeout] || 2).to_i
|
|
226
|
+
end
|
|
227
|
+
|
|
221
228
|
def retry_deadline
|
|
222
229
|
if @config[:retry_deadline]
|
|
223
230
|
@config[:retry_deadline].to_f
|
|
@@ -344,6 +351,13 @@ module ActiveRecord
|
|
|
344
351
|
Process.clock_gettime(Process::CLOCK_MONOTONIC) - @idle_since
|
|
345
352
|
end
|
|
346
353
|
|
|
354
|
+
# Seconds since this connection last communicated with the server
|
|
355
|
+
def seconds_since_last_activity # :nodoc:
|
|
356
|
+
if @raw_connection && @last_activity
|
|
357
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC) - @last_activity
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
|
|
347
361
|
def unprepared_statement
|
|
348
362
|
cache = prepared_statements_disabled_cache.add?(object_id) if @prepared_statements
|
|
349
363
|
yield
|
|
@@ -663,11 +677,12 @@ module ActiveRecord
|
|
|
663
677
|
|
|
664
678
|
enable_lazy_transactions!
|
|
665
679
|
@raw_connection_dirty = false
|
|
680
|
+
@last_activity = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
666
681
|
@verified = true
|
|
667
682
|
|
|
668
683
|
reset_transaction(restore: restore_transactions) do
|
|
669
684
|
clear_cache!(new_connection: true)
|
|
670
|
-
|
|
685
|
+
attempt_configure_connection
|
|
671
686
|
end
|
|
672
687
|
rescue => original_exception
|
|
673
688
|
translated_exception = translate_exception_class(original_exception, nil, nil)
|
|
@@ -682,6 +697,7 @@ module ActiveRecord
|
|
|
682
697
|
end
|
|
683
698
|
end
|
|
684
699
|
|
|
700
|
+
@last_activity = nil
|
|
685
701
|
@verified = false
|
|
686
702
|
|
|
687
703
|
raise translated_exception
|
|
@@ -719,7 +735,7 @@ module ActiveRecord
|
|
|
719
735
|
def reset!
|
|
720
736
|
clear_cache!(new_connection: true)
|
|
721
737
|
reset_transaction
|
|
722
|
-
|
|
738
|
+
attempt_configure_connection
|
|
723
739
|
end
|
|
724
740
|
|
|
725
741
|
# Removes the connection from the pool and disconnect it.
|
|
@@ -755,7 +771,8 @@ module ActiveRecord
|
|
|
755
771
|
if @unconfigured_connection
|
|
756
772
|
@raw_connection = @unconfigured_connection
|
|
757
773
|
@unconfigured_connection = nil
|
|
758
|
-
|
|
774
|
+
attempt_configure_connection
|
|
775
|
+
@last_activity = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
759
776
|
@verified = true
|
|
760
777
|
return
|
|
761
778
|
end
|
|
@@ -985,6 +1002,9 @@ module ActiveRecord
|
|
|
985
1002
|
if @verified
|
|
986
1003
|
# Cool, we're confident the connection's ready to use. (Note this might have
|
|
987
1004
|
# become true during the above #materialize_transactions.)
|
|
1005
|
+
elsif (last_activity = seconds_since_last_activity) && last_activity < verify_timeout
|
|
1006
|
+
# We haven't actually verified the connection since we acquired it, but it
|
|
1007
|
+
# has been used very recently. We're going to assume it's still okay.
|
|
988
1008
|
elsif reconnectable
|
|
989
1009
|
if allow_retry
|
|
990
1010
|
# Not sure about the connection yet, but if anything goes wrong we can
|
|
@@ -1026,6 +1046,7 @@ module ActiveRecord
|
|
|
1026
1046
|
# Barring a known-retryable error inside the query (regardless of
|
|
1027
1047
|
# whether we were in a _position_ to retry it), we should infer that
|
|
1028
1048
|
# there's likely a real problem with the connection.
|
|
1049
|
+
@last_activity = nil
|
|
1029
1050
|
@verified = false
|
|
1030
1051
|
end
|
|
1031
1052
|
|
|
@@ -1040,6 +1061,7 @@ module ActiveRecord
|
|
|
1040
1061
|
# `with_raw_connection` block only when the block is guaranteed to
|
|
1041
1062
|
# exercise the raw connection.
|
|
1042
1063
|
def verified!
|
|
1064
|
+
@last_activity = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
1043
1065
|
@verified = true
|
|
1044
1066
|
end
|
|
1045
1067
|
|
|
@@ -1205,6 +1227,13 @@ module ActiveRecord
|
|
|
1205
1227
|
check_version
|
|
1206
1228
|
end
|
|
1207
1229
|
|
|
1230
|
+
def attempt_configure_connection
|
|
1231
|
+
configure_connection
|
|
1232
|
+
rescue Exception # Need to handle things such as Timeout::ExitException
|
|
1233
|
+
disconnect!
|
|
1234
|
+
raise
|
|
1235
|
+
end
|
|
1236
|
+
|
|
1208
1237
|
def default_prepared_statements
|
|
1209
1238
|
true
|
|
1210
1239
|
end
|
|
@@ -174,6 +174,10 @@ module ActiveRecord
|
|
|
174
174
|
mariadb? && database_version >= "10.5.0"
|
|
175
175
|
end
|
|
176
176
|
|
|
177
|
+
def return_value_after_insert?(column) # :nodoc:
|
|
178
|
+
supports_insert_returning? ? column.auto_populated? : column.auto_increment?
|
|
179
|
+
end
|
|
180
|
+
|
|
177
181
|
def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
|
|
178
182
|
query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
|
|
179
183
|
end
|
|
@@ -414,7 +418,11 @@ module ActiveRecord
|
|
|
414
418
|
type ||= column.sql_type
|
|
415
419
|
|
|
416
420
|
unless options.key?(:default)
|
|
417
|
-
options[:default] = column.
|
|
421
|
+
options[:default] = if column.default_function
|
|
422
|
+
-> { column.default_function }
|
|
423
|
+
else
|
|
424
|
+
column.default
|
|
425
|
+
end
|
|
418
426
|
end
|
|
419
427
|
|
|
420
428
|
unless options.key?(:null)
|
|
@@ -639,24 +647,26 @@ module ActiveRecord
|
|
|
639
647
|
end
|
|
640
648
|
|
|
641
649
|
def build_insert_sql(insert) # :nodoc:
|
|
650
|
+
# Can use any column as it will be assigned to itself.
|
|
642
651
|
no_op_column = quote_column_name(insert.keys.first) if insert.keys.first
|
|
643
652
|
|
|
644
653
|
# MySQL 8.0.19 replaces `VALUES(<expression>)` clauses with row and column alias names, see https://dev.mysql.com/worklog/task/?id=6312 .
|
|
645
654
|
# then MySQL 8.0.20 deprecates the `VALUES(<expression>)` see https://dev.mysql.com/worklog/task/?id=13325 .
|
|
646
655
|
if supports_insert_raw_alias_syntax?
|
|
656
|
+
quoted_table_name = insert.model.quoted_table_name
|
|
647
657
|
values_alias = quote_table_name("#{insert.model.table_name.parameterize}_values")
|
|
648
658
|
sql = +"INSERT #{insert.into} #{insert.values_list} AS #{values_alias}"
|
|
649
659
|
|
|
650
660
|
if insert.skip_duplicates?
|
|
651
661
|
if no_op_column
|
|
652
|
-
sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{
|
|
662
|
+
sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{quoted_table_name}.#{no_op_column}"
|
|
653
663
|
end
|
|
654
664
|
elsif insert.update_duplicates?
|
|
655
665
|
if insert.raw_update_sql?
|
|
656
666
|
sql = +"INSERT #{insert.into} #{insert.values_list} ON DUPLICATE KEY UPDATE #{insert.raw_update_sql}"
|
|
657
667
|
else
|
|
658
668
|
sql << " ON DUPLICATE KEY UPDATE "
|
|
659
|
-
sql << insert.touch_model_timestamps_unless { |column| "#{
|
|
669
|
+
sql << insert.touch_model_timestamps_unless { |column| "#{quoted_table_name}.#{column}<=>#{values_alias}.#{column}" }
|
|
660
670
|
sql << insert.updatable_columns.map { |column| "#{column}=#{values_alias}.#{column}" }.join(",")
|
|
661
671
|
end
|
|
662
672
|
end
|
|
@@ -757,9 +767,7 @@ module ActiveRecord
|
|
|
757
767
|
|
|
758
768
|
private
|
|
759
769
|
def strip_whitespace_characters(expression)
|
|
760
|
-
expression
|
|
761
|
-
expression = expression.gsub(/\s{2,}/, " ")
|
|
762
|
-
expression
|
|
770
|
+
expression.gsub('\\\n', "").gsub("x0A", "").squish
|
|
763
771
|
end
|
|
764
772
|
|
|
765
773
|
def extended_type_map_key
|
|
@@ -110,7 +110,13 @@ module ActiveRecord
|
|
|
110
110
|
else
|
|
111
111
|
value.getlocal
|
|
112
112
|
end
|
|
113
|
-
when
|
|
113
|
+
when Time
|
|
114
|
+
if default_timezone == :utc
|
|
115
|
+
value.utc? ? value : value.getutc
|
|
116
|
+
else
|
|
117
|
+
value.utc? ? value.getlocal : value
|
|
118
|
+
end
|
|
119
|
+
when Date
|
|
114
120
|
value
|
|
115
121
|
else
|
|
116
122
|
super
|
|
@@ -23,7 +23,7 @@ module ActiveRecord
|
|
|
23
23
|
end
|
|
24
24
|
end
|
|
25
25
|
else
|
|
26
|
-
exec_stmt_and_free(sql, name, binds, cache_stmt: prepare, async: async) do |_, result|
|
|
26
|
+
exec_stmt_and_free(sql, name, binds, cache_stmt: prepare, async: async, allow_retry: allow_retry) do |_, result|
|
|
27
27
|
if result
|
|
28
28
|
build_result(columns: result.fields, rows: result.to_a)
|
|
29
29
|
else
|
|
@@ -105,7 +105,7 @@ module ActiveRecord
|
|
|
105
105
|
end
|
|
106
106
|
end
|
|
107
107
|
|
|
108
|
-
def exec_stmt_and_free(sql, name, binds, cache_stmt: false, async: false)
|
|
108
|
+
def exec_stmt_and_free(sql, name, binds, cache_stmt: false, async: false, allow_retry: false)
|
|
109
109
|
sql = transform_query(sql)
|
|
110
110
|
check_if_write_query(sql)
|
|
111
111
|
|
|
@@ -114,7 +114,7 @@ module ActiveRecord
|
|
|
114
114
|
type_casted_binds = type_casted_binds(binds)
|
|
115
115
|
|
|
116
116
|
log(sql, name, binds, type_casted_binds, async: async) do |notification_payload|
|
|
117
|
-
with_raw_connection do |conn|
|
|
117
|
+
with_raw_connection(allow_retry: allow_retry) do |conn|
|
|
118
118
|
sync_timezone_changes(conn)
|
|
119
119
|
|
|
120
120
|
if cache_stmt
|
|
@@ -113,7 +113,14 @@ module ActiveRecord
|
|
|
113
113
|
end
|
|
114
114
|
|
|
115
115
|
def active?
|
|
116
|
-
connected?
|
|
116
|
+
if connected?
|
|
117
|
+
@lock.synchronize do
|
|
118
|
+
if @raw_connection&.ping
|
|
119
|
+
verified!
|
|
120
|
+
true
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end || false
|
|
117
124
|
end
|
|
118
125
|
|
|
119
126
|
alias :reset! :reconnect!
|