activerecord 8.1.0.beta1 → 8.1.0.rc1
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 +105 -4
- data/lib/active_record/associations/belongs_to_association.rb +2 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +10 -2
- data/lib/active_record/attribute_methods.rb +1 -1
- data/lib/active_record/autosave_association.rb +2 -2
- data/lib/active_record/base.rb +2 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +1 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +31 -29
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +32 -13
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +17 -8
- data/lib/active_record/connection_adapters/abstract/transaction.rb +9 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +25 -11
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +0 -2
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +1 -1
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +0 -2
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +8 -1
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +7 -5
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +7 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +16 -5
- data/lib/active_record/connection_handling.rb +2 -1
- data/lib/active_record/database_configurations/hash_config.rb +5 -2
- data/lib/active_record/encryption/encryptor.rb +12 -0
- data/lib/active_record/errors.rb +3 -3
- data/lib/active_record/explain.rb +1 -1
- data/lib/active_record/explain_registry.rb +51 -1
- data/lib/active_record/gem_version.rb +1 -1
- data/lib/active_record/log_subscriber.rb +1 -1
- data/lib/active_record/migration/compatibility.rb +1 -1
- data/lib/active_record/model_schema.rb +26 -3
- data/lib/active_record/railties/controller_runtime.rb +11 -6
- data/lib/active_record/railties/databases.rake +1 -1
- data/lib/active_record/railties/job_runtime.rb +2 -2
- data/lib/active_record/relation/batches.rb +3 -3
- data/lib/active_record/relation/merger.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +7 -7
- data/lib/active_record/relation/predicate_builder.rb +7 -5
- data/lib/active_record/relation/query_methods.rb +1 -1
- data/lib/active_record/relation/where_clause.rb +2 -0
- data/lib/active_record/relation.rb +1 -1
- data/lib/active_record/runtime_registry.rb +41 -58
- data/lib/active_record/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +5 -20
- data/lib/active_record/tasks/database_tasks.rb +24 -14
- data/lib/active_record/test_databases.rb +4 -2
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- metadata +9 -9
- data/lib/active_record/explain_subscriber.rb +0 -34
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0f6a8286538d4ed09b1508bed7ded4b5b40629d71cbee9467c8926968413da05
|
4
|
+
data.tar.gz: ef5f9492d1efa2285bb03fdfe9d808e86ffadc768f0265257aba7d93bd5c32ad
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cad145e87e54f6bfd6cc35294e4aa25471f705e08eae4497a10c75af5ff204b8205728bf41e0ff43d1a2713c712d53b56ba35fbdfdb2df8a3204edc8c9c46eef
|
7
|
+
data.tar.gz: a8792d05c5c847e1c22daff04e84a662afd6847f07368fea7be61876c434f6e233f1d295c296f0c40adaa24896def1f985edc241c589489e4153554a4b15d5c7
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,108 @@
|
|
1
|
+
## Rails 8.1.0.rc1 (October 15, 2025) ##
|
2
|
+
|
3
|
+
* Add replicas to test database parallelization setup.
|
4
|
+
|
5
|
+
Setup and configuration of databases for parallel testing now includes replicas.
|
6
|
+
|
7
|
+
This fixes an issue when using a replica database, database selector middleware,
|
8
|
+
and non-transactional tests, where integration tests running in parallel would select
|
9
|
+
the base test database, i.e. `db_test`, instead of the numbered parallel worker database,
|
10
|
+
i.e. `db_test_{n}`.
|
11
|
+
|
12
|
+
*Adam Maas*
|
13
|
+
|
14
|
+
* Support virtual (not persisted) generated columns on PostgreSQL 18+
|
15
|
+
|
16
|
+
PostgreSQL 18 introduces virtual (not persisted) generated columns,
|
17
|
+
which are now the default unless the `stored: true` option is explicitly specified on PostgreSQL 18+.
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
create_table :users do |t|
|
21
|
+
t.string :name
|
22
|
+
t.virtual :lower_name, type: :string, as: "LOWER(name)", stored: false
|
23
|
+
t.virtual :name_length, type: :integer, as: "LENGTH(name)"
|
24
|
+
end
|
25
|
+
```
|
26
|
+
|
27
|
+
*Yasuo Honda*
|
28
|
+
|
29
|
+
* Optimize schema dumping to prevent duplicate file generation.
|
30
|
+
|
31
|
+
`ActiveRecord::Tasks::DatabaseTasks.dump_all` now tracks which schema files
|
32
|
+
have already been dumped and skips dumping the same file multiple times.
|
33
|
+
This improves performance when multiple database configurations share the
|
34
|
+
same schema dump path.
|
35
|
+
|
36
|
+
*Mikey Gough*, *Hartley McGuire*
|
37
|
+
|
38
|
+
* Add structured events for Active Record:
|
39
|
+
- `active_record.strict_loading_violation`
|
40
|
+
- `active_record.sql`
|
41
|
+
|
42
|
+
*Gannon McGibbon*
|
43
|
+
|
44
|
+
* Add support for integer shard keys.
|
45
|
+
```ruby
|
46
|
+
# Now accepts symbols as shard keys.
|
47
|
+
ActiveRecord::Base.connects_to(shards: {
|
48
|
+
1: { writing: :primary_shard_one, reading: :primary_shard_one },
|
49
|
+
2: { writing: :primary_shard_two, reading: :primary_shard_two},
|
50
|
+
})
|
51
|
+
|
52
|
+
ActiveRecord::Base.connected_to(shard: 1) do
|
53
|
+
# ..
|
54
|
+
end
|
55
|
+
```
|
56
|
+
|
57
|
+
*Nony Dutton*
|
58
|
+
|
59
|
+
* Add `ActiveRecord::Base.only_columns`
|
60
|
+
|
61
|
+
Similar in use case to `ignored_columns` but listing columns to consider rather than the ones
|
62
|
+
to ignore.
|
63
|
+
|
64
|
+
Can be useful when working with a legacy or shared database schema, or to make safe schema change
|
65
|
+
in two deploys rather than three.
|
66
|
+
|
67
|
+
*Anton Kandratski*
|
68
|
+
|
69
|
+
* Use `PG::Connection#close_prepared` (protocol level Close) to deallocate
|
70
|
+
prepared statements when available.
|
71
|
+
|
72
|
+
To enable its use, you must have pg >= 1.6.0, libpq >= 17, and a PostgreSQL
|
73
|
+
database version >= 17.
|
74
|
+
|
75
|
+
*Hartley McGuire*, *Andrew Jackson*
|
76
|
+
|
77
|
+
* Fix query cache for pinned connections in multi threaded transactional tests
|
78
|
+
|
79
|
+
When a pinned connection is used across separate threads, they now use a separate cache store
|
80
|
+
for each thread.
|
81
|
+
|
82
|
+
This improve accuracy of system tests, and any test using multiple threads.
|
83
|
+
|
84
|
+
*Heinrich Lee Yu*, *Jean Boussier*
|
85
|
+
|
86
|
+
* Fix time attribute dirty tracking with timezone conversions.
|
87
|
+
|
88
|
+
Time-only attributes now maintain a fixed date of 2000-01-01 during timezone conversions,
|
89
|
+
preventing them from being incorrectly marked as changed due to date shifts.
|
90
|
+
|
91
|
+
This fixes an issue where time attributes would be marked as changed when setting the same time value
|
92
|
+
due to timezone conversion causing internal date shifts.
|
93
|
+
|
94
|
+
*Prateek Choudhary*
|
95
|
+
|
96
|
+
* Skip calling `PG::Connection#cancel` in `cancel_any_running_query`
|
97
|
+
when using libpq >= 18 with pg < 1.6.0, due to incompatibility.
|
98
|
+
Rollback still runs, but may take longer.
|
99
|
+
|
100
|
+
*Yasuo Honda*, *Lars Kanis*
|
101
|
+
|
102
|
+
* Don't add `id_value` attribute alias when attribute/column with that name already exists.
|
103
|
+
|
104
|
+
*Rob Lewis*
|
105
|
+
|
1
106
|
## Rails 8.1.0.beta1 (September 04, 2025) ##
|
2
107
|
|
3
108
|
* Remove deprecated `:unsigned_float` and `:unsigned_decimal` column methods for MySQL.
|
@@ -71,10 +176,6 @@
|
|
71
176
|
|
72
177
|
*Kir Shatrov*
|
73
178
|
|
74
|
-
* Emit a warning for pg gem < 1.6.0 when using PostgreSQL 18+
|
75
|
-
|
76
|
-
*Yasuo Honda*
|
77
|
-
|
78
179
|
* Fix `#merge` with `#or` or `#and` and a mixture of attributes and SQL strings resulting in an incorrect query.
|
79
180
|
|
80
181
|
```ruby
|
@@ -135,7 +135,9 @@ module ActiveRecord
|
|
135
135
|
target_key_values = record ? Array(primary_key(record.class)).map { |key| record._read_attribute(key) } : []
|
136
136
|
|
137
137
|
if force || reflection_fk.map { |fk| owner._read_attribute(fk) } != target_key_values
|
138
|
+
owner_pk = Array(owner.class.primary_key)
|
138
139
|
reflection_fk.each_with_index do |key, index|
|
140
|
+
next if record.nil? && owner_pk.include?(key)
|
139
141
|
owner[key] = target_key_values[index]
|
140
142
|
end
|
141
143
|
end
|
@@ -21,7 +21,11 @@ module ActiveRecord
|
|
21
21
|
set_time_zone_without_conversion(super)
|
22
22
|
elsif value.respond_to?(:in_time_zone)
|
23
23
|
begin
|
24
|
-
super(user_input_in_time_zone(value)) || super
|
24
|
+
result = super(user_input_in_time_zone(value)) || super
|
25
|
+
if result && type == :time
|
26
|
+
result = result.change(year: 2000, month: 1, day: 1)
|
27
|
+
end
|
28
|
+
result
|
25
29
|
rescue ArgumentError
|
26
30
|
nil
|
27
31
|
end
|
@@ -41,7 +45,11 @@ module ActiveRecord
|
|
41
45
|
return if value.nil?
|
42
46
|
|
43
47
|
if value.acts_like?(:time)
|
44
|
-
value.in_time_zone
|
48
|
+
converted = value.in_time_zone
|
49
|
+
if type == :time && converted
|
50
|
+
converted = converted.change(year: 2000, month: 1, day: 1)
|
51
|
+
end
|
52
|
+
converted
|
45
53
|
elsif value.respond_to?(:infinite?) && value.infinite?
|
46
54
|
value
|
47
55
|
else
|
@@ -113,7 +113,7 @@ module ActiveRecord
|
|
113
113
|
unless abstract_class?
|
114
114
|
load_schema
|
115
115
|
super(attribute_names)
|
116
|
-
alias_attribute :id_value, :id if _has_attribute?("id")
|
116
|
+
alias_attribute :id_value, :id if _has_attribute?("id") && !_has_attribute?("id_value")
|
117
117
|
end
|
118
118
|
|
119
119
|
generate_alias_attributes
|
@@ -374,7 +374,7 @@ module ActiveRecord
|
|
374
374
|
context = validation_context if custom_validation_context?
|
375
375
|
return true if record.valid?(context)
|
376
376
|
|
377
|
-
if
|
377
|
+
if context || record.changed_for_autosave?
|
378
378
|
associated_errors = record.errors.objects
|
379
379
|
else
|
380
380
|
# If there are existing invalid records in the DB, we should still be able to reference them.
|
@@ -527,7 +527,7 @@ module ActiveRecord
|
|
527
527
|
return false unless reflection.inverse_of&.polymorphic?
|
528
528
|
|
529
529
|
class_name = record._read_attribute(reflection.inverse_of.foreign_type)
|
530
|
-
reflection.active_record !=
|
530
|
+
reflection.active_record.polymorphic_name != class_name
|
531
531
|
end
|
532
532
|
|
533
533
|
# Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
|
data/lib/active_record/base.rb
CHANGED
@@ -6,7 +6,6 @@ require "active_support/descendants_tracker"
|
|
6
6
|
require "active_support/time"
|
7
7
|
require "active_support/core_ext/class/subclasses"
|
8
8
|
require "active_record/log_subscriber"
|
9
|
-
require "active_record/explain_subscriber"
|
10
9
|
require "active_record/relation/delegation"
|
11
10
|
require "active_record/attributes"
|
12
11
|
require "active_record/type_caster"
|
@@ -256,13 +255,13 @@ module ActiveRecord # :nodoc:
|
|
256
255
|
# * AssociationTypeMismatch - The object assigned to the association wasn't of the type
|
257
256
|
# specified in the association definition.
|
258
257
|
# * AttributeAssignmentError - An error occurred while doing a mass assignment through the
|
259
|
-
# {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] method.
|
258
|
+
# {ActiveRecord::Base#attributes=}[rdoc-ref:ActiveModel::AttributeAssignment#attributes=] method.
|
260
259
|
# You can inspect the +attribute+ property of the exception object to determine which attribute
|
261
260
|
# triggered the error.
|
262
261
|
# * ConnectionNotEstablished - No connection has been established.
|
263
262
|
# Use {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection] before querying.
|
264
263
|
# * MultiparameterAssignmentErrors - Collection of errors that occurred during a mass assignment using the
|
265
|
-
# {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] method.
|
264
|
+
# {ActiveRecord::Base#attributes=}[rdoc-ref:ActiveModel::AttributeAssignment#attributes=] method.
|
266
265
|
# The +errors+ property of this exception contains an array of
|
267
266
|
# AttributeAssignmentError
|
268
267
|
# objects that should be inspected to determine which attributes triggered the errors.
|
@@ -129,9 +129,7 @@ module ActiveRecord
|
|
129
129
|
t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
130
130
|
elapsed = 0
|
131
131
|
loop do
|
132
|
-
|
133
|
-
@cond.wait(timeout - elapsed)
|
134
|
-
end
|
132
|
+
@cond.wait(timeout - elapsed)
|
135
133
|
|
136
134
|
return remove if any?
|
137
135
|
|
@@ -8,12 +8,7 @@ require "active_record/connection_adapters/abstract/connection_pool/reaper"
|
|
8
8
|
|
9
9
|
module ActiveRecord
|
10
10
|
module ConnectionAdapters
|
11
|
-
module AbstractPool # :nodoc:
|
12
|
-
end
|
13
|
-
|
14
11
|
class NullPool # :nodoc:
|
15
|
-
include ConnectionAdapters::AbstractPool
|
16
|
-
|
17
12
|
class NullConfig
|
18
13
|
def method_missing(...)
|
19
14
|
nil
|
@@ -36,6 +31,7 @@ module ActiveRecord
|
|
36
31
|
end
|
37
32
|
|
38
33
|
def schema_cache; end
|
34
|
+
def query_cache; end
|
39
35
|
def connection_descriptor; end
|
40
36
|
def checkin(_); end
|
41
37
|
def remove(_); end
|
@@ -116,6 +112,7 @@ module ActiveRecord
|
|
116
112
|
# * +max_age+: number of seconds the pool will allow the connection to
|
117
113
|
# exist before retiring it at next checkin. (default Float::INFINITY).
|
118
114
|
# * +max_connections+: maximum number of connections the pool may manage (default 5).
|
115
|
+
# Set to +nil+ or -1 for unlimited connections.
|
119
116
|
# * +min_connections+: minimum number of connections the pool will open and maintain (default 0).
|
120
117
|
# * +pool_jitter+: maximum reduction factor to apply to +max_age+ and
|
121
118
|
# +keepalive+ intervals (default 0.2; range 0.0-1.0).
|
@@ -183,21 +180,30 @@ module ActiveRecord
|
|
183
180
|
end
|
184
181
|
end
|
185
182
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
183
|
+
if RUBY_ENGINE == "ruby"
|
184
|
+
# Thanks to the GVL, the LeaseRegistry doesn't need to be synchronized on MRI
|
185
|
+
class LeaseRegistry < WeakThreadKeyMap # :nodoc:
|
186
|
+
def [](context)
|
187
|
+
super || (self[context] = Lease.new)
|
188
|
+
end
|
190
189
|
end
|
190
|
+
else
|
191
|
+
class LeaseRegistry # :nodoc:
|
192
|
+
def initialize
|
193
|
+
@mutex = Mutex.new
|
194
|
+
@map = WeakThreadKeyMap.new
|
195
|
+
end
|
191
196
|
|
192
|
-
|
193
|
-
|
194
|
-
|
197
|
+
def [](context)
|
198
|
+
@mutex.synchronize do
|
199
|
+
@map[context] ||= Lease.new
|
200
|
+
end
|
195
201
|
end
|
196
|
-
end
|
197
202
|
|
198
|
-
|
199
|
-
|
200
|
-
|
203
|
+
def clear
|
204
|
+
@mutex.synchronize do
|
205
|
+
@map.clear
|
206
|
+
end
|
201
207
|
end
|
202
208
|
end
|
203
209
|
end
|
@@ -229,7 +235,6 @@ module ActiveRecord
|
|
229
235
|
|
230
236
|
include MonitorMixin
|
231
237
|
prepend QueryCache::ConnectionPoolConfiguration
|
232
|
-
include ConnectionAdapters::AbstractPool
|
233
238
|
|
234
239
|
attr_accessor :automatic_reconnect, :checkout_timeout
|
235
240
|
attr_reader :db_config, :max_connections, :min_connections, :max_age, :keepalive, :reaper, :pool_config, :async_executor, :role, :shard
|
@@ -349,8 +354,9 @@ module ActiveRecord
|
|
349
354
|
# held in a cache keyed by a thread.
|
350
355
|
def lease_connection
|
351
356
|
lease = connection_lease
|
352
|
-
lease.sticky = true
|
353
357
|
lease.connection ||= checkout
|
358
|
+
lease.sticky = true
|
359
|
+
lease.connection
|
354
360
|
end
|
355
361
|
|
356
362
|
def permanent_lease? # :nodoc:
|
@@ -368,6 +374,7 @@ module ActiveRecord
|
|
368
374
|
end
|
369
375
|
|
370
376
|
@pinned_connection.lock_thread = ActiveSupport::IsolatedExecutionState.context if lock_thread
|
377
|
+
@pinned_connection.pinned = true
|
371
378
|
@pinned_connection.verify! # eagerly validate the connection
|
372
379
|
@pinned_connection.begin_transaction joinable: false, _lazy: false
|
373
380
|
end
|
@@ -390,6 +397,7 @@ module ActiveRecord
|
|
390
397
|
end
|
391
398
|
|
392
399
|
if @pinned_connection.nil?
|
400
|
+
connection.pinned = false
|
393
401
|
connection.steal!
|
394
402
|
connection.lock_thread = nil
|
395
403
|
checkin(connection)
|
@@ -653,11 +661,7 @@ module ActiveRecord
|
|
653
661
|
conn.lock.synchronize do
|
654
662
|
synchronize do
|
655
663
|
connection_lease.clear(conn)
|
656
|
-
|
657
|
-
conn._run_checkin_callbacks do
|
658
|
-
conn.expire
|
659
|
-
end
|
660
|
-
|
664
|
+
conn.expire
|
661
665
|
@available.add conn
|
662
666
|
end
|
663
667
|
end
|
@@ -784,6 +788,7 @@ module ActiveRecord
|
|
784
788
|
|
785
789
|
if need_new_connections
|
786
790
|
while new_conn = try_to_checkout_new_connection { @connections.size < @min_connections }
|
791
|
+
new_conn.allow_preconnect = true
|
787
792
|
checkin(new_conn)
|
788
793
|
end
|
789
794
|
end
|
@@ -929,7 +934,7 @@ module ActiveRecord
|
|
929
934
|
# Each connection will only be processed once per call to this method,
|
930
935
|
# but (particularly in the async case) there is no protection against
|
931
936
|
# a second call to this method starting to work through the list
|
932
|
-
# before the first call has completed. (Though regular pool
|
937
|
+
# before the first call has completed. (Though regular pool behavior
|
933
938
|
# will prevent two instances from working on the same specific
|
934
939
|
# connection at the same time.)
|
935
940
|
def sequential_maintenance(candidate_selector, &maintenance_work)
|
@@ -1220,7 +1225,7 @@ module ActiveRecord
|
|
1220
1225
|
do_checkout = synchronize do
|
1221
1226
|
return if self.discarded?
|
1222
1227
|
|
1223
|
-
if @threads_blocking_new_connections.zero? && (@connections.size + @now_connecting) < @max_connections && (!block_given? || yield)
|
1228
|
+
if @threads_blocking_new_connections.zero? && (@max_connections.nil? || (@connections.size + @now_connecting) < @max_connections) && (!block_given? || yield)
|
1224
1229
|
if @connections.size > 0 || @original_context != ActiveSupport::IsolatedExecutionState.context
|
1225
1230
|
@activated = true
|
1226
1231
|
end
|
@@ -1267,10 +1272,7 @@ module ActiveRecord
|
|
1267
1272
|
end
|
1268
1273
|
|
1269
1274
|
def checkout_and_verify(c)
|
1270
|
-
c.
|
1271
|
-
c.clean!
|
1272
|
-
end
|
1273
|
-
c
|
1275
|
+
c.clean!
|
1274
1276
|
rescue Exception
|
1275
1277
|
remove c
|
1276
1278
|
c.disconnect!
|
@@ -353,6 +353,22 @@ module ActiveRecord
|
|
353
353
|
# isolation level.
|
354
354
|
# :args: (requires_new: nil, isolation: nil, &block)
|
355
355
|
def transaction(requires_new: nil, isolation: nil, joinable: true, &block)
|
356
|
+
# If we're running inside the single, non-joinable transaction that
|
357
|
+
# ActiveRecord::TestFixtures starts around each example (depth == 1),
|
358
|
+
# an `isolation:` hint must be validated then ignored so that the
|
359
|
+
# adapter isn't asked to change the isolation level mid-transaction.
|
360
|
+
if isolation && !requires_new && open_transactions == 1 && !current_transaction.joinable?
|
361
|
+
iso = isolation.to_sym
|
362
|
+
|
363
|
+
unless transaction_isolation_levels.include?(iso)
|
364
|
+
raise ActiveRecord::TransactionIsolationError,
|
365
|
+
"invalid transaction isolation level: #{iso.inspect}"
|
366
|
+
end
|
367
|
+
|
368
|
+
current_transaction.isolation = iso
|
369
|
+
isolation = nil
|
370
|
+
end
|
371
|
+
|
356
372
|
if !requires_new && current_transaction.joinable?
|
357
373
|
if isolation && current_transaction.isolation != isolation
|
358
374
|
raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction"
|
@@ -372,10 +388,10 @@ module ActiveRecord
|
|
372
388
|
:disable_lazy_transactions!, :enable_lazy_transactions!, :dirty_current_transaction,
|
373
389
|
to: :transaction_manager
|
374
390
|
|
375
|
-
def
|
391
|
+
def mark_transaction_written # :nodoc:
|
376
392
|
transaction = current_transaction
|
377
393
|
if transaction.open?
|
378
|
-
transaction.written ||=
|
394
|
+
transaction.written ||= true
|
379
395
|
end
|
380
396
|
end
|
381
397
|
|
@@ -420,13 +436,16 @@ module ActiveRecord
|
|
420
436
|
end
|
421
437
|
end
|
422
438
|
|
439
|
+
TRANSACTION_ISOLATION_LEVELS = {
|
440
|
+
read_uncommitted: "READ UNCOMMITTED",
|
441
|
+
read_committed: "READ COMMITTED",
|
442
|
+
repeatable_read: "REPEATABLE READ",
|
443
|
+
serializable: "SERIALIZABLE"
|
444
|
+
}.freeze
|
445
|
+
private_constant :TRANSACTION_ISOLATION_LEVELS
|
446
|
+
|
423
447
|
def transaction_isolation_levels
|
424
|
-
|
425
|
-
read_uncommitted: "READ UNCOMMITTED",
|
426
|
-
read_committed: "READ COMMITTED",
|
427
|
-
repeatable_read: "REPEATABLE READ",
|
428
|
-
serializable: "SERIALIZABLE"
|
429
|
-
}
|
448
|
+
TRANSACTION_ISOLATION_LEVELS
|
430
449
|
end
|
431
450
|
|
432
451
|
# Begins the transaction with the isolation level set. Raises an error by
|
@@ -549,9 +568,7 @@ module ActiveRecord
|
|
549
568
|
type_casted_binds = type_casted_binds(binds)
|
550
569
|
log(sql, name, binds, type_casted_binds, async: async, allow_retry: allow_retry) do |notification_payload|
|
551
570
|
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
|
552
|
-
result =
|
553
|
-
perform_query(conn, sql, binds, type_casted_binds, prepare: prepare, notification_payload: notification_payload, batch: batch)
|
554
|
-
end
|
571
|
+
result = perform_query(conn, sql, binds, type_casted_binds, prepare: prepare, notification_payload: notification_payload, batch: batch)
|
555
572
|
handle_warnings(result, sql)
|
556
573
|
result
|
557
574
|
end
|
@@ -575,8 +592,10 @@ module ActiveRecord
|
|
575
592
|
end
|
576
593
|
|
577
594
|
def preprocess_query(sql)
|
578
|
-
|
579
|
-
|
595
|
+
if write_query?(sql)
|
596
|
+
ensure_writes_are_allowed(sql)
|
597
|
+
mark_transaction_written
|
598
|
+
end
|
580
599
|
|
581
600
|
# We call tranformers after the write checks so we don't add extra parsing work.
|
582
601
|
# This means we assume no transformer whille change a read for a write
|
@@ -13,8 +13,6 @@ module ActiveRecord
|
|
13
13
|
dirties_query_cache base, :exec_query, :execute, :create, :insert, :update, :delete, :truncate,
|
14
14
|
:truncate_tables, :rollback_to_savepoint, :rollback_db_transaction, :restart_db_transaction,
|
15
15
|
:exec_insert_all
|
16
|
-
|
17
|
-
base.set_callback :checkin, :after, :unset_query_cache!
|
18
16
|
end
|
19
17
|
|
20
18
|
def dirties_query_cache(base, *method_names)
|
@@ -209,15 +207,26 @@ module ActiveRecord
|
|
209
207
|
end
|
210
208
|
end
|
211
209
|
|
212
|
-
attr_accessor :query_cache
|
213
|
-
|
214
210
|
def initialize(*)
|
215
211
|
super
|
216
212
|
@query_cache = nil
|
217
213
|
end
|
218
214
|
|
215
|
+
attr_writer :query_cache
|
216
|
+
|
217
|
+
def query_cache
|
218
|
+
if @pinned && @owner != ActiveSupport::IsolatedExecutionState.context
|
219
|
+
# With transactional tests, if the connection is pinned, any thread
|
220
|
+
# other than the one that pinned the connection need to go through the
|
221
|
+
# query cache pool, so each thread get a different cache.
|
222
|
+
pool.query_cache
|
223
|
+
else
|
224
|
+
@query_cache
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
219
228
|
def query_cache_enabled
|
220
|
-
|
229
|
+
query_cache&.enabled?
|
221
230
|
end
|
222
231
|
|
223
232
|
# Enable the query cache within the block.
|
@@ -256,7 +265,7 @@ module ActiveRecord
|
|
256
265
|
|
257
266
|
# If arel is locked this is a SELECT ... FOR UPDATE or somesuch.
|
258
267
|
# Such queries should not be cached.
|
259
|
-
if
|
268
|
+
if query_cache_enabled && !(arel.respond_to?(:locked) && arel.locked)
|
260
269
|
sql, binds, preparable, allow_retry = to_sql_and_binds(arel, binds, preparable, allow_retry)
|
261
270
|
|
262
271
|
if async
|
@@ -280,7 +289,7 @@ module ActiveRecord
|
|
280
289
|
|
281
290
|
result = nil
|
282
291
|
@lock.synchronize do
|
283
|
-
result =
|
292
|
+
result = query_cache[key]
|
284
293
|
end
|
285
294
|
|
286
295
|
if result
|
@@ -299,7 +308,7 @@ module ActiveRecord
|
|
299
308
|
hit = true
|
300
309
|
|
301
310
|
@lock.synchronize do
|
302
|
-
result =
|
311
|
+
result = query_cache.compute_if_absent(key) do
|
303
312
|
hit = false
|
304
313
|
yield
|
305
314
|
end
|
@@ -124,6 +124,7 @@ module ActiveRecord
|
|
124
124
|
def after_commit; yield; end
|
125
125
|
def after_rollback; end
|
126
126
|
def user_transaction; ActiveRecord::Transaction::NULL_TRANSACTION; end
|
127
|
+
def isolation=(_); end
|
127
128
|
end
|
128
129
|
|
129
130
|
class Transaction # :nodoc:
|
@@ -156,6 +157,10 @@ module ActiveRecord
|
|
156
157
|
@isolation_level
|
157
158
|
end
|
158
159
|
|
160
|
+
def isolation=(isolation) # :nodoc:
|
161
|
+
@isolation_level = isolation
|
162
|
+
end
|
163
|
+
|
159
164
|
def initialize(connection, isolation: nil, joinable: true, run_commit_callbacks: false)
|
160
165
|
super()
|
161
166
|
@connection = connection
|
@@ -426,6 +431,10 @@ module ActiveRecord
|
|
426
431
|
@parent_transaction.isolation
|
427
432
|
end
|
428
433
|
|
434
|
+
def isolation=(isolation) # :nodoc:
|
435
|
+
@parent_transaction.isolation = isolation
|
436
|
+
end
|
437
|
+
|
429
438
|
def materialize!
|
430
439
|
connection.create_savepoint(savepoint_name)
|
431
440
|
super
|
@@ -5,6 +5,7 @@ require "active_record/connection_adapters/abstract/schema_dumper"
|
|
5
5
|
require "active_record/connection_adapters/abstract/schema_creation"
|
6
6
|
require "active_support/concurrency/null_lock"
|
7
7
|
require "active_support/concurrency/load_interlock_aware_monitor"
|
8
|
+
require "active_support/concurrency/thread_monitor"
|
8
9
|
require "arel/collectors/bind"
|
9
10
|
require "arel/collectors/composite"
|
10
11
|
require "arel/collectors/sql_string"
|
@@ -42,7 +43,8 @@ module ActiveRecord
|
|
42
43
|
|
43
44
|
attr_reader :pool
|
44
45
|
attr_reader :visitor, :owner, :logger, :lock
|
45
|
-
|
46
|
+
attr_reader :allow_preconnect # :nodoc:
|
47
|
+
attr_accessor :pinned # :nodoc:
|
46
48
|
alias :in_use? :owner
|
47
49
|
|
48
50
|
def pool=(value)
|
@@ -51,7 +53,11 @@ module ActiveRecord
|
|
51
53
|
@pool = value
|
52
54
|
end
|
53
55
|
|
54
|
-
|
56
|
+
def allow_preconnect=(value) # :nodoc:
|
57
|
+
@lock.synchronize do
|
58
|
+
@allow_preconnect = value
|
59
|
+
end
|
60
|
+
end
|
55
61
|
|
56
62
|
def self.type_cast_config_to_integer(config)
|
57
63
|
if config.is_a?(Integer)
|
@@ -153,9 +159,10 @@ module ActiveRecord
|
|
153
159
|
end
|
154
160
|
|
155
161
|
@owner = nil
|
162
|
+
@pinned = false
|
156
163
|
@pool = ActiveRecord::ConnectionAdapters::NullPool.new
|
157
164
|
@idle_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
158
|
-
@allow_preconnect =
|
165
|
+
@allow_preconnect = false
|
159
166
|
@visitor = arel_visitor
|
160
167
|
@statements = build_statement_pool
|
161
168
|
self.lock_thread = nil
|
@@ -188,16 +195,16 @@ module ActiveRecord
|
|
188
195
|
@lock =
|
189
196
|
case lock_thread
|
190
197
|
when Thread
|
191
|
-
ActiveSupport::Concurrency::
|
198
|
+
ActiveSupport::Concurrency::ThreadMonitor.new
|
192
199
|
when Fiber
|
193
|
-
|
200
|
+
::Monitor.new
|
194
201
|
else
|
195
202
|
ActiveSupport::Concurrency::NullLock
|
196
203
|
end
|
197
204
|
end
|
198
205
|
|
199
|
-
def
|
200
|
-
if preventing_writes?
|
206
|
+
def ensure_writes_are_allowed(sql) # :nodoc:
|
207
|
+
if preventing_writes?
|
201
208
|
raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
|
202
209
|
end
|
203
210
|
end
|
@@ -327,8 +334,12 @@ module ActiveRecord
|
|
327
334
|
"Current thread: #{ActiveSupport::IsolatedExecutionState.context}."
|
328
335
|
end
|
329
336
|
|
330
|
-
|
331
|
-
|
337
|
+
_run_checkin_callbacks do
|
338
|
+
@idle_since = Process.clock_gettime(Process::CLOCK_MONOTONIC) if update_idle
|
339
|
+
@owner = nil
|
340
|
+
enable_lazy_transactions!
|
341
|
+
unset_query_cache!
|
342
|
+
end
|
332
343
|
else
|
333
344
|
raise ActiveRecordError, "Cannot expire connection, it is not currently leased."
|
334
345
|
end
|
@@ -825,8 +836,11 @@ module ActiveRecord
|
|
825
836
|
end
|
826
837
|
|
827
838
|
def clean! # :nodoc:
|
828
|
-
|
829
|
-
|
839
|
+
_run_checkout_callbacks do
|
840
|
+
@raw_connection_dirty = false
|
841
|
+
@verified = nil
|
842
|
+
end
|
843
|
+
self
|
830
844
|
end
|
831
845
|
|
832
846
|
def verified? # :nodoc:
|
@@ -209,8 +209,6 @@ module ActiveRecord
|
|
209
209
|
}
|
210
210
|
end
|
211
211
|
|
212
|
-
# HELPER METHODS ===========================================
|
213
|
-
|
214
212
|
# Must return the MySQL error number from the exception, if the exception has an
|
215
213
|
# error number.
|
216
214
|
def error_number(exception) # :nodoc:
|
@@ -48,7 +48,7 @@ module ActiveRecord
|
|
48
48
|
end
|
49
49
|
|
50
50
|
# = Active Record MySQL Adapter \Index Definition
|
51
|
-
class IndexDefinition < ActiveRecord::ConnectionAdapters::IndexDefinition
|
51
|
+
class IndexDefinition < ActiveRecord::ConnectionAdapters::IndexDefinition # :nodoc:
|
52
52
|
attr_accessor :enabled
|
53
53
|
|
54
54
|
def initialize(*args, **kwargs)
|