activerecord 8.0.1 → 8.0.2
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 +138 -1
- data/lib/active_record/associations/alias_tracker.rb +6 -4
- data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
- data/lib/active_record/attributes.rb +2 -2
- data/lib/active_record/autosave_association.rb +21 -11
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +17 -14
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +14 -9
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
- data/lib/active_record/connection_adapters/abstract_adapter.rb +39 -22
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +4 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +8 -1
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +12 -12
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +7 -2
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +6 -1
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -1
- data/lib/active_record/core.rb +31 -2
- data/lib/active_record/counter_cache.rb +1 -1
- data/lib/active_record/delegated_type.rb +17 -17
- data/lib/active_record/future_result.rb +3 -3
- data/lib/active_record/gem_version.rb +1 -1
- data/lib/active_record/migration/command_recorder.rb +5 -2
- data/lib/active_record/railties/databases.rake +1 -1
- data/lib/active_record/relation/calculations.rb +23 -17
- data/lib/active_record/relation.rb +1 -1
- data/lib/active_record/signed_id.rb +4 -3
- data/lib/active_record/statement_cache.rb +2 -2
- data/lib/active_record/transactions.rb +5 -6
- data/lib/arel/collectors/bind.rb +1 -1
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +2 -2
- data/lib/arel/nodes/binary.rb +1 -1
- data/lib/arel/nodes/node.rb +1 -1
- data/lib/arel/nodes/sql_literal.rb +1 -1
- data/lib/arel/visitors/to_sql.rb +1 -1
- metadata +10 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 82c8ca907ebe4ec74622fb27ce477cdd5a4536e05bb25334c3551560324507cd
|
4
|
+
data.tar.gz: 88787eb24c8acb59dc3517d85e919a50d85bf3c0bff149f59f76d2bf6b8495d8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ea3714004780235f5bb1c78e8b667d229e642942b3ea12163b923d72fa66668e1bd9fc848c87347d96a83c85d3934e499411118883fd2a9c38779a87f031d83c
|
7
|
+
data.tar.gz: 7bacd8bf3eb5712fecdcbba881a2a92f9f4f6fe609c644802b12e423a3f95e7b5953669715ad4215b7170a80f37c70686d2bf090872ef585fb0f4ed8001ff03e
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,143 @@
|
|
1
|
+
## Rails 8.0.2 (March 12, 2025) ##
|
2
|
+
|
3
|
+
* No changes.
|
4
|
+
|
5
|
+
|
6
|
+
## Rails 8.0.2 (March 12, 2025) ##
|
7
|
+
|
8
|
+
* Fix inverting `rename_enum_value` when `:from`/`:to` are provided.
|
9
|
+
|
10
|
+
*fatkodima*
|
11
|
+
|
12
|
+
* Prevent persisting invalid record.
|
13
|
+
|
14
|
+
*Edouard Chin*
|
15
|
+
|
16
|
+
* Fix inverting `drop_table` without options.
|
17
|
+
|
18
|
+
*fatkodima*
|
19
|
+
|
20
|
+
* Fix count with group by qualified name on loaded relation.
|
21
|
+
|
22
|
+
*Ryuta Kamizono*
|
23
|
+
|
24
|
+
* Fix `sum` with qualified name on loaded relation.
|
25
|
+
|
26
|
+
*Chris Gunther*
|
27
|
+
|
28
|
+
* The SQLite3 adapter quotes non-finite Numeric values like "Infinity" and "NaN".
|
29
|
+
|
30
|
+
*Mike Dalessio*
|
31
|
+
|
32
|
+
* Handle libpq returning a database version of 0 on no/bad connection in `PostgreSQLAdapter`.
|
33
|
+
|
34
|
+
Before, this version would be cached and an error would be raised during connection configuration when
|
35
|
+
comparing it with the minimum required version for the adapter. This meant that the connection could
|
36
|
+
never be successfully configured on subsequent reconnection attempts.
|
37
|
+
|
38
|
+
Now, this is treated as a connection failure consistent with libpq, raising a `ActiveRecord::ConnectionFailed`
|
39
|
+
and ensuring the version isn't cached, which allows the version to be retrieved on the next connection attempt.
|
40
|
+
|
41
|
+
*Joshua Young*, *Rian McGuire*
|
42
|
+
|
43
|
+
* Fix error handling during connection configuration.
|
44
|
+
|
45
|
+
Active Record wasn't properly handling errors during the connection configuration phase.
|
46
|
+
This could lead to a partially configured connection being used, resulting in various exceptions,
|
47
|
+
the most common being with the PostgreSQLAdapter raising `undefined method `key?' for nil`
|
48
|
+
or `TypeError: wrong argument type nil (expected PG::TypeMap)`.
|
49
|
+
|
50
|
+
*Jean Boussier*
|
51
|
+
|
52
|
+
* Fix a case where a non-retryable query could be marked retryable.
|
53
|
+
|
54
|
+
*Hartley McGuire*
|
55
|
+
|
56
|
+
* Handle circular references when autosaving associations.
|
57
|
+
|
58
|
+
*zzak*
|
59
|
+
|
60
|
+
* PoolConfig no longer keeps a reference to the connection class.
|
61
|
+
|
62
|
+
Keeping a reference to the class caused subtle issues when combined with reloading in
|
63
|
+
development. Fixes #54343.
|
64
|
+
|
65
|
+
*Mike Dalessio*
|
66
|
+
|
67
|
+
* Fix SQL notifications sometimes not sent when using async queries.
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
Post.async_count
|
71
|
+
ActiveSupport::Notifications.subscribed(->(*) { "Will never reach here" }) do
|
72
|
+
Post.count
|
73
|
+
end
|
74
|
+
```
|
75
|
+
|
76
|
+
In rare circumstances and under the right race condition, Active Support notifications
|
77
|
+
would no longer be dispatched after using an asynchronous query.
|
78
|
+
This is now fixed.
|
79
|
+
|
80
|
+
*Edouard Chin*
|
81
|
+
|
82
|
+
* Fix support for PostgreSQL enum types with commas in their name.
|
83
|
+
|
84
|
+
*Arthur Hess*
|
85
|
+
|
86
|
+
* Fix inserts on MySQL with no RETURNING support for a table with multiple auto populated columns.
|
87
|
+
|
88
|
+
*Nikita Vasilevsky*
|
89
|
+
|
90
|
+
* Fix joining on a scoped association with string joins and bind parameters.
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
class Instructor < ActiveRecord::Base
|
94
|
+
has_many :instructor_roles, -> { active }
|
95
|
+
end
|
96
|
+
|
97
|
+
class InstructorRole < ActiveRecord::Base
|
98
|
+
scope :active, -> {
|
99
|
+
joins("JOIN students ON instructor_roles.student_id = students.id")
|
100
|
+
.where(students { status: 1 })
|
101
|
+
}
|
102
|
+
end
|
103
|
+
|
104
|
+
Instructor.joins(:instructor_roles).first
|
105
|
+
```
|
106
|
+
|
107
|
+
The above example would result in `ActiveRecord::StatementInvalid` because the
|
108
|
+
`active` scope bind parameters would be lost.
|
109
|
+
|
110
|
+
*Jean Boussier*
|
111
|
+
|
112
|
+
* Fix a potential race condition with system tests and transactional fixtures.
|
113
|
+
|
114
|
+
*Sjoerd Lagarde*
|
115
|
+
|
116
|
+
* Fix autosave associations to no longer validated unmodified associated records.
|
117
|
+
|
118
|
+
Active Record was incorrectly performing validation on associated record that
|
119
|
+
weren't created nor modified as part of the transaction:
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
Post.create!(author: User.find(1)) # Fail if user is invalid
|
123
|
+
```
|
124
|
+
|
125
|
+
*Jean Boussier*
|
126
|
+
|
127
|
+
* Remember when a database connection has recently been verified (for
|
128
|
+
two seconds, by default), to avoid repeated reverifications during a
|
129
|
+
single request.
|
130
|
+
|
131
|
+
This should recreate a similar rate of verification as in Rails 7.1,
|
132
|
+
where connections are leased for the duration of a request, and thus
|
133
|
+
only verified once.
|
134
|
+
|
135
|
+
*Matthew Draper*
|
136
|
+
|
137
|
+
|
1
138
|
## Rails 8.0.1 (December 13, 2024) ##
|
2
139
|
|
3
|
-
* Fix removing foreign keys with :restrict action for
|
140
|
+
* Fix removing foreign keys with :restrict action for MySQL.
|
4
141
|
|
5
142
|
*fatkodima*
|
6
143
|
|
@@ -26,16 +26,18 @@ module ActiveRecord
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def self.initial_count_for(connection, name, table_joins)
|
29
|
-
|
29
|
+
quoted_name_escaped = nil
|
30
|
+
name_escaped = nil
|
30
31
|
|
31
32
|
counts = table_joins.map do |join|
|
32
33
|
if join.is_a?(Arel::Nodes::StringJoin)
|
33
|
-
#
|
34
|
-
|
34
|
+
# quoted_name_escaped should be case ignored as some database adapters (Oracle) return quoted name in uppercase
|
35
|
+
quoted_name_escaped ||= Regexp.escape(connection.quote_table_name(name))
|
36
|
+
name_escaped ||= Regexp.escape(name)
|
35
37
|
|
36
38
|
# Table names + table aliases
|
37
39
|
join.left.scan(
|
38
|
-
/JOIN(?:\s+\w+)?\s+(?:\S+\s+)?(?:#{
|
40
|
+
/JOIN(?:\s+\w+)?\s+(?:\S+\s+)?(?:#{quoted_name_escaped}|#{name_escaped})\sON/i
|
39
41
|
).size
|
40
42
|
elsif join.is_a?(Arel::Nodes::Join)
|
41
43
|
join.left.name == name ? 1 : 0
|
@@ -38,41 +38,39 @@ module ActiveRecord
|
|
38
38
|
chain << [reflection, table]
|
39
39
|
end
|
40
40
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
klass = reflection.klass
|
41
|
+
# The chain starts with the target table, but we want to end with it here (makes
|
42
|
+
# more sense in this context), so we reverse
|
43
|
+
chain.reverse_each do |reflection, table|
|
44
|
+
klass = reflection.klass
|
46
45
|
|
47
|
-
|
46
|
+
scope = reflection.join_scope(table, foreign_table, foreign_klass)
|
48
47
|
|
49
|
-
|
50
|
-
|
48
|
+
unless scope.references_values.empty?
|
49
|
+
associations = scope.eager_load_values | scope.includes_values
|
51
50
|
|
52
|
-
|
53
|
-
|
54
|
-
end
|
51
|
+
unless associations.empty?
|
52
|
+
scope.joins! scope.construct_join_dependency(associations, Arel::Nodes::OuterJoin)
|
55
53
|
end
|
54
|
+
end
|
56
55
|
|
57
|
-
|
58
|
-
|
56
|
+
arel = scope.arel(alias_tracker.aliases)
|
57
|
+
nodes = arel.constraints.first
|
59
58
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
end
|
59
|
+
if nodes.is_a?(Arel::Nodes::And)
|
60
|
+
others = nodes.children.extract! do |node|
|
61
|
+
!Arel.fetch_attribute(node) { |attr| attr.relation.name == table.name }
|
64
62
|
end
|
63
|
+
end
|
65
64
|
|
66
|
-
|
65
|
+
joins << join_type.new(table, Arel::Nodes::On.new(nodes))
|
67
66
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
end
|
72
|
-
|
73
|
-
# The current table in this iteration becomes the foreign table in the next
|
74
|
-
foreign_table, foreign_klass = table, klass
|
67
|
+
if others && !others.empty?
|
68
|
+
joins.concat arel.join_sources
|
69
|
+
append_constraints(joins.last, others)
|
75
70
|
end
|
71
|
+
|
72
|
+
# The current table in this iteration becomes the foreign table in the next
|
73
|
+
foreign_table, foreign_klass = table, klass
|
76
74
|
end
|
77
75
|
|
78
76
|
joins
|
@@ -91,10 +89,10 @@ module ActiveRecord
|
|
91
89
|
end
|
92
90
|
|
93
91
|
private
|
94
|
-
def append_constraints(
|
92
|
+
def append_constraints(join, constraints)
|
95
93
|
if join.is_a?(Arel::Nodes::StringJoin)
|
96
94
|
join_string = Arel::Nodes::And.new(constraints.unshift join.left)
|
97
|
-
join.left =
|
95
|
+
join.left = join_string
|
98
96
|
else
|
99
97
|
right = join.right
|
100
98
|
right.expr = Arel::Nodes::And.new(constraints.unshift right.expr)
|
@@ -178,8 +178,8 @@ module ActiveRecord
|
|
178
178
|
# @currency_converter = currency_converter
|
179
179
|
# end
|
180
180
|
#
|
181
|
-
# # value will be the result of
|
182
|
-
# #
|
181
|
+
# # value will be the result of #deserialize or
|
182
|
+
# # #cast. Assumed to be an instance of Money in
|
183
183
|
# # this case.
|
184
184
|
# def serialize(value)
|
185
185
|
# value_in_bitcoins = @currency_converter.convert_to_bitcoins(value)
|
@@ -372,19 +372,29 @@ module ActiveRecord
|
|
372
372
|
return true if record.destroyed? || (association.options[:autosave] && record.marked_for_destruction?)
|
373
373
|
|
374
374
|
context = validation_context if custom_validation_context?
|
375
|
+
return true if record.valid?(context)
|
375
376
|
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
}
|
383
|
-
else
|
384
|
-
errors.add(association.reflection.name)
|
385
|
-
end
|
377
|
+
if record.changed? || record.new_record? || context
|
378
|
+
associated_errors = record.errors.objects
|
379
|
+
else
|
380
|
+
# If there are existing invalid records in the DB, we should still be able to reference them.
|
381
|
+
# Unless a record (no matter where in the association chain) is invalid and is being changed.
|
382
|
+
associated_errors = record.errors.objects.select { |error| error.is_a?(Associations::NestedError) }
|
386
383
|
end
|
387
|
-
|
384
|
+
|
385
|
+
if association.options[:autosave]
|
386
|
+
return if equal?(record)
|
387
|
+
|
388
|
+
associated_errors.each { |error|
|
389
|
+
errors.objects.append(
|
390
|
+
Associations::NestedError.new(association, error)
|
391
|
+
)
|
392
|
+
}
|
393
|
+
elsif associated_errors.any?
|
394
|
+
errors.add(association.reflection.name)
|
395
|
+
end
|
396
|
+
|
397
|
+
errors.any?
|
388
398
|
end
|
389
399
|
|
390
400
|
# Is used as an around_save callback to check while saving a collection
|
@@ -54,19 +54,22 @@ module ActiveRecord
|
|
54
54
|
# about the model. The model needs to pass a connection specification name to the handler,
|
55
55
|
# in order to look up the correct connection pool.
|
56
56
|
class ConnectionHandler
|
57
|
-
class
|
58
|
-
|
59
|
-
|
60
|
-
def initialize(name)
|
57
|
+
class ConnectionDescriptor # :nodoc:
|
58
|
+
def initialize(name, primary = false)
|
61
59
|
@name = name
|
60
|
+
@primary = primary
|
61
|
+
end
|
62
|
+
|
63
|
+
def name
|
64
|
+
primary_class? ? "ActiveRecord::Base" : @name
|
62
65
|
end
|
63
66
|
|
64
67
|
def primary_class?
|
65
|
-
|
68
|
+
@primary
|
66
69
|
end
|
67
70
|
|
68
71
|
def current_preventing_writes
|
69
|
-
|
72
|
+
ActiveRecord::Base.preventing_writes?(@name)
|
70
73
|
end
|
71
74
|
end
|
72
75
|
|
@@ -115,7 +118,7 @@ module ActiveRecord
|
|
115
118
|
pool_config = resolve_pool_config(config, owner_name, role, shard)
|
116
119
|
db_config = pool_config.db_config
|
117
120
|
|
118
|
-
pool_manager = set_pool_manager(pool_config.
|
121
|
+
pool_manager = set_pool_manager(pool_config.connection_descriptor)
|
119
122
|
|
120
123
|
# If there is an existing pool with the same values as the pool_config
|
121
124
|
# don't remove the connection. Connections should only be removed if we are
|
@@ -127,8 +130,8 @@ module ActiveRecord
|
|
127
130
|
# Update the pool_config's connection class if it differs. This is used
|
128
131
|
# for ensuring that ActiveRecord::Base and the primary_abstract_class use
|
129
132
|
# the same pool. Without this granular swapping will not work correctly.
|
130
|
-
if owner_name.primary_class? && (existing_pool_config.
|
131
|
-
existing_pool_config.
|
133
|
+
if owner_name.primary_class? && (existing_pool_config.connection_descriptor != owner_name)
|
134
|
+
existing_pool_config.connection_descriptor = owner_name
|
132
135
|
end
|
133
136
|
|
134
137
|
existing_pool_config.pool
|
@@ -137,7 +140,7 @@ module ActiveRecord
|
|
137
140
|
pool_manager.set_pool_config(role, shard, pool_config)
|
138
141
|
|
139
142
|
payload = {
|
140
|
-
connection_name: pool_config.
|
143
|
+
connection_name: pool_config.connection_descriptor.name,
|
141
144
|
role: role,
|
142
145
|
shard: shard,
|
143
146
|
config: db_config.configuration_hash
|
@@ -242,8 +245,8 @@ module ActiveRecord
|
|
242
245
|
end
|
243
246
|
|
244
247
|
# Get the existing pool manager or initialize and assign a new one.
|
245
|
-
def set_pool_manager(
|
246
|
-
connection_name_to_pool_manager[
|
248
|
+
def set_pool_manager(connection_descriptor)
|
249
|
+
connection_name_to_pool_manager[connection_descriptor.name] ||= PoolManager.new
|
247
250
|
end
|
248
251
|
|
249
252
|
def pool_managers
|
@@ -278,9 +281,9 @@ module ActiveRecord
|
|
278
281
|
|
279
282
|
def determine_owner_name(owner_name, config)
|
280
283
|
if owner_name.is_a?(String) || owner_name.is_a?(Symbol)
|
281
|
-
|
284
|
+
ConnectionDescriptor.new(owner_name.to_s)
|
282
285
|
elsif config.is_a?(Symbol)
|
283
|
-
|
286
|
+
ConnectionDescriptor.new(config.to_s)
|
284
287
|
else
|
285
288
|
owner_name
|
286
289
|
end
|
@@ -36,7 +36,7 @@ module ActiveRecord
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def schema_cache; end
|
39
|
-
def
|
39
|
+
def connection_descriptor; end
|
40
40
|
def checkin(_); end
|
41
41
|
def remove(_); end
|
42
42
|
def async_executor; end
|
@@ -364,8 +364,8 @@ module ActiveRecord
|
|
364
364
|
clean
|
365
365
|
end
|
366
366
|
|
367
|
-
def
|
368
|
-
pool_config.
|
367
|
+
def connection_descriptor # :nodoc:
|
368
|
+
pool_config.connection_descriptor
|
369
369
|
end
|
370
370
|
|
371
371
|
# Returns true if there is an open connection being used for the current thread.
|
@@ -545,20 +545,25 @@ module ActiveRecord
|
|
545
545
|
# Raises:
|
546
546
|
# - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool.
|
547
547
|
def checkout(checkout_timeout = @checkout_timeout)
|
548
|
-
|
549
|
-
|
550
|
-
|
548
|
+
return checkout_and_verify(acquire_connection(checkout_timeout)) unless @pinned_connection
|
549
|
+
|
550
|
+
@pinned_connection.lock.synchronize do
|
551
|
+
synchronize do
|
552
|
+
# The pinned connection may have been cleaned up before we synchronized, so check if it is still present
|
553
|
+
if @pinned_connection
|
551
554
|
@pinned_connection.verify!
|
555
|
+
|
552
556
|
# Any leased connection must be in @connections otherwise
|
553
557
|
# some methods like #connected? won't behave correctly
|
554
558
|
unless @connections.include?(@pinned_connection)
|
555
559
|
@connections << @pinned_connection
|
556
560
|
end
|
561
|
+
|
562
|
+
@pinned_connection
|
563
|
+
else
|
564
|
+
checkout_and_verify(acquire_connection(checkout_timeout))
|
557
565
|
end
|
558
566
|
end
|
559
|
-
@pinned_connection
|
560
|
-
else
|
561
|
-
checkout_and_verify(acquire_connection(checkout_timeout))
|
562
567
|
end
|
563
568
|
end
|
564
569
|
|
@@ -150,7 +150,6 @@ module ActiveRecord
|
|
150
150
|
end
|
151
151
|
|
152
152
|
@owner = nil
|
153
|
-
@instrumenter = ActiveSupport::Notifications.instrumenter
|
154
153
|
@pool = ActiveRecord::ConnectionAdapters::NullPool.new
|
155
154
|
@idle_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
156
155
|
@visitor = arel_visitor
|
@@ -168,6 +167,7 @@ module ActiveRecord
|
|
168
167
|
@default_timezone = self.class.validate_default_timezone(@config[:default_timezone])
|
169
168
|
|
170
169
|
@raw_connection_dirty = false
|
170
|
+
@last_activity = nil
|
171
171
|
@verified = false
|
172
172
|
end
|
173
173
|
|
@@ -190,19 +190,6 @@ module ActiveRecord
|
|
190
190
|
end
|
191
191
|
end
|
192
192
|
|
193
|
-
EXCEPTION_NEVER = { Exception => :never }.freeze # :nodoc:
|
194
|
-
EXCEPTION_IMMEDIATE = { Exception => :immediate }.freeze # :nodoc:
|
195
|
-
private_constant :EXCEPTION_NEVER, :EXCEPTION_IMMEDIATE
|
196
|
-
def with_instrumenter(instrumenter, &block) # :nodoc:
|
197
|
-
Thread.handle_interrupt(EXCEPTION_NEVER) do
|
198
|
-
previous_instrumenter = @instrumenter
|
199
|
-
@instrumenter = instrumenter
|
200
|
-
Thread.handle_interrupt(EXCEPTION_IMMEDIATE, &block)
|
201
|
-
ensure
|
202
|
-
@instrumenter = previous_instrumenter
|
203
|
-
end
|
204
|
-
end
|
205
|
-
|
206
193
|
def check_if_write_query(sql) # :nodoc:
|
207
194
|
if preventing_writes? && write_query?(sql)
|
208
195
|
raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
|
@@ -217,6 +204,10 @@ module ActiveRecord
|
|
217
204
|
(@config[:connection_retries] || 1).to_i
|
218
205
|
end
|
219
206
|
|
207
|
+
def verify_timeout
|
208
|
+
(@config[:verify_timeout] || 2).to_i
|
209
|
+
end
|
210
|
+
|
220
211
|
def retry_deadline
|
221
212
|
if @config[:retry_deadline]
|
222
213
|
@config[:retry_deadline].to_f
|
@@ -235,9 +226,9 @@ module ActiveRecord
|
|
235
226
|
# the value of +current_preventing_writes+.
|
236
227
|
def preventing_writes?
|
237
228
|
return true if replica?
|
238
|
-
return false if
|
229
|
+
return false if connection_descriptor.nil?
|
239
230
|
|
240
|
-
|
231
|
+
connection_descriptor.current_preventing_writes
|
241
232
|
end
|
242
233
|
|
243
234
|
def prepared_statements?
|
@@ -288,8 +279,8 @@ module ActiveRecord
|
|
288
279
|
@owner = ActiveSupport::IsolatedExecutionState.context
|
289
280
|
end
|
290
281
|
|
291
|
-
def
|
292
|
-
@pool.
|
282
|
+
def connection_descriptor # :nodoc:
|
283
|
+
@pool.connection_descriptor
|
293
284
|
end
|
294
285
|
|
295
286
|
# The role (e.g. +:writing+) for the current connection. In a
|
@@ -343,6 +334,13 @@ module ActiveRecord
|
|
343
334
|
Process.clock_gettime(Process::CLOCK_MONOTONIC) - @idle_since
|
344
335
|
end
|
345
336
|
|
337
|
+
# Seconds since this connection last communicated with the server
|
338
|
+
def seconds_since_last_activity # :nodoc:
|
339
|
+
if @raw_connection && @last_activity
|
340
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC) - @last_activity
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
346
344
|
def unprepared_statement
|
347
345
|
cache = prepared_statements_disabled_cache.add?(object_id) if @prepared_statements
|
348
346
|
yield
|
@@ -670,11 +668,12 @@ module ActiveRecord
|
|
670
668
|
|
671
669
|
enable_lazy_transactions!
|
672
670
|
@raw_connection_dirty = false
|
671
|
+
@last_activity = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
673
672
|
@verified = true
|
674
673
|
|
675
674
|
reset_transaction(restore: restore_transactions) do
|
676
675
|
clear_cache!(new_connection: true)
|
677
|
-
|
676
|
+
attempt_configure_connection
|
678
677
|
end
|
679
678
|
rescue => original_exception
|
680
679
|
translated_exception = translate_exception_class(original_exception, nil, nil)
|
@@ -689,6 +688,7 @@ module ActiveRecord
|
|
689
688
|
end
|
690
689
|
end
|
691
690
|
|
691
|
+
@last_activity = nil
|
692
692
|
@verified = false
|
693
693
|
|
694
694
|
raise translated_exception
|
@@ -726,7 +726,7 @@ module ActiveRecord
|
|
726
726
|
def reset!
|
727
727
|
clear_cache!(new_connection: true)
|
728
728
|
reset_transaction
|
729
|
-
|
729
|
+
attempt_configure_connection
|
730
730
|
end
|
731
731
|
|
732
732
|
# Removes the connection from the pool and disconnect it.
|
@@ -762,7 +762,8 @@ module ActiveRecord
|
|
762
762
|
if @unconfigured_connection
|
763
763
|
@raw_connection = @unconfigured_connection
|
764
764
|
@unconfigured_connection = nil
|
765
|
-
|
765
|
+
attempt_configure_connection
|
766
|
+
@last_activity = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
766
767
|
@verified = true
|
767
768
|
return
|
768
769
|
end
|
@@ -992,6 +993,9 @@ module ActiveRecord
|
|
992
993
|
if @verified
|
993
994
|
# Cool, we're confident the connection's ready to use. (Note this might have
|
994
995
|
# become true during the above #materialize_transactions.)
|
996
|
+
elsif (last_activity = seconds_since_last_activity) && last_activity < verify_timeout
|
997
|
+
# We haven't actually verified the connection since we acquired it, but it
|
998
|
+
# has been used very recently. We're going to assume it's still okay.
|
995
999
|
elsif reconnectable
|
996
1000
|
if allow_retry
|
997
1001
|
# Not sure about the connection yet, but if anything goes wrong we can
|
@@ -1033,6 +1037,7 @@ module ActiveRecord
|
|
1033
1037
|
# Barring a known-retryable error inside the query (regardless of
|
1034
1038
|
# whether we were in a _position_ to retry it), we should infer that
|
1035
1039
|
# there's likely a real problem with the connection.
|
1040
|
+
@last_activity = nil
|
1036
1041
|
@verified = false
|
1037
1042
|
end
|
1038
1043
|
|
@@ -1047,6 +1052,7 @@ module ActiveRecord
|
|
1047
1052
|
# `with_raw_connection` block only when the block is guaranteed to
|
1048
1053
|
# exercise the raw connection.
|
1049
1054
|
def verified!
|
1055
|
+
@last_activity = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
1050
1056
|
@verified = true
|
1051
1057
|
end
|
1052
1058
|
|
@@ -1126,7 +1132,7 @@ module ActiveRecord
|
|
1126
1132
|
end
|
1127
1133
|
|
1128
1134
|
def log(sql, name = "SQL", binds = [], type_casted_binds = [], async: false, &block) # :doc:
|
1129
|
-
|
1135
|
+
instrumenter.instrument(
|
1130
1136
|
"sql.active_record",
|
1131
1137
|
sql: sql,
|
1132
1138
|
name: name,
|
@@ -1142,6 +1148,10 @@ module ActiveRecord
|
|
1142
1148
|
raise ex.set_query(sql, binds)
|
1143
1149
|
end
|
1144
1150
|
|
1151
|
+
def instrumenter # :nodoc:
|
1152
|
+
ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] ||= ActiveSupport::Notifications.instrumenter
|
1153
|
+
end
|
1154
|
+
|
1145
1155
|
def translate_exception(exception, message:, sql:, binds:)
|
1146
1156
|
# override in derived class
|
1147
1157
|
case exception
|
@@ -1203,6 +1213,13 @@ module ActiveRecord
|
|
1203
1213
|
check_version
|
1204
1214
|
end
|
1205
1215
|
|
1216
|
+
def attempt_configure_connection
|
1217
|
+
configure_connection
|
1218
|
+
rescue
|
1219
|
+
disconnect!
|
1220
|
+
raise
|
1221
|
+
end
|
1222
|
+
|
1206
1223
|
def default_prepared_statements
|
1207
1224
|
true
|
1208
1225
|
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
|
@@ -106,7 +106,14 @@ module ActiveRecord
|
|
106
106
|
end
|
107
107
|
|
108
108
|
def active?
|
109
|
-
connected?
|
109
|
+
if connected?
|
110
|
+
@lock.synchronize do
|
111
|
+
if @raw_connection&.ping
|
112
|
+
verified!
|
113
|
+
true
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end || false
|
110
117
|
end
|
111
118
|
|
112
119
|
alias :reset! :reconnect!
|