mirror of https://github.com/rails/rails
Add dirties option to uncached (#51204)
This adds a `dirties` option to `ActiveRecord::Base.uncached` and `ActiveRecord::ConnectionAdapters::ConnectionPool#uncached`. Setting `dirties` to `false`, means database writes to the connection pool will not mark any query caches as dirty. The option defaults to `true` which retains the existing behaviour and clears query caches on all connection pools used by the current thread. Co-authored-by: Jeremy Daer <jeremy@rubyonrails.org>
This commit is contained in:
parent
5cedb8745c
commit
5d528ba0c8
|
@ -1,3 +1,15 @@
|
|||
* Add dirties option to uncached
|
||||
|
||||
This adds a `dirties` option to `ActiveRecord::Base.uncached` and
|
||||
`ActiveRecord::ConnectionAdapters::ConnectionPool#uncached`.
|
||||
|
||||
When set to `true` (the default), writes will clear all query caches belonging to the current thread.
|
||||
When set to `false`, writes to the affected connection pool will not clear any query cache.
|
||||
|
||||
This is needed by Solid Cache so that cache writes do not clear query caches.
|
||||
|
||||
*Donal McBreen*
|
||||
|
||||
* Deprecate `ActiveRecord::Base.connection` in favor of `.lease_connection`
|
||||
|
||||
The method has been renamed as `lease_connection` to better reflect that the returned
|
||||
|
|
|
@ -41,9 +41,14 @@ module ActiveRecord
|
|||
def checkin(_); end
|
||||
def remove(_); end
|
||||
def async_executor; end
|
||||
|
||||
def db_config
|
||||
NULL_CONFIG
|
||||
end
|
||||
|
||||
def dirties_query_cache
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
# = Active Record Connection Pool
|
||||
|
|
|
@ -20,7 +20,9 @@ module ActiveRecord
|
|||
method_names.each do |method_name|
|
||||
base.class_eval <<-end_code, __FILE__, __LINE__ + 1
|
||||
def #{method_name}(...)
|
||||
ActiveRecord::Base.clear_query_caches_for_current_thread
|
||||
if pool.dirties_query_cache
|
||||
ActiveRecord::Base.clear_query_caches_for_current_thread
|
||||
end
|
||||
super
|
||||
end
|
||||
end_code
|
||||
|
@ -29,13 +31,15 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
class Store # :nodoc:
|
||||
attr_accessor :enabled
|
||||
attr_accessor :enabled, :dirties
|
||||
alias_method :enabled?, :enabled
|
||||
alias_method :dirties?, :dirties
|
||||
|
||||
def initialize(max_size)
|
||||
@map = {}
|
||||
@max_size = max_size
|
||||
@enabled = false
|
||||
@dirties = true
|
||||
end
|
||||
|
||||
def size
|
||||
|
@ -96,38 +100,42 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
# Disable the query cache within the block.
|
||||
def disable_query_cache
|
||||
def disable_query_cache(dirties: true)
|
||||
cache = query_cache
|
||||
old, cache.enabled = cache.enabled, false
|
||||
old_enabled, cache.enabled, old_dirties, cache.dirties = cache.enabled, false, cache.dirties, dirties
|
||||
begin
|
||||
yield
|
||||
ensure
|
||||
cache.enabled = old
|
||||
cache.enabled, cache.dirties = old_enabled, old_dirties
|
||||
end
|
||||
end
|
||||
|
||||
def enable_query_cache
|
||||
cache = query_cache
|
||||
old, cache.enabled = cache.enabled, true
|
||||
old_enabled, cache.enabled, old_dirties, cache.dirties = cache.enabled, true, cache.dirties, true
|
||||
begin
|
||||
yield
|
||||
ensure
|
||||
cache.enabled = old
|
||||
cache.enabled, cache.dirties = old_enabled, old_dirties
|
||||
end
|
||||
end
|
||||
|
||||
def enable_query_cache!
|
||||
query_cache.enabled = true
|
||||
query_cache.enabled, query_cache.dirties = true, true
|
||||
end
|
||||
|
||||
def disable_query_cache!
|
||||
query_cache.enabled = false
|
||||
query_cache.enabled, query_cache.dirties = false, true
|
||||
end
|
||||
|
||||
def query_cache_enabled
|
||||
query_cache.enabled
|
||||
end
|
||||
|
||||
def dirties_query_cache
|
||||
query_cache.dirties
|
||||
end
|
||||
|
||||
def clear_query_cache
|
||||
if @pinned_connection
|
||||
# With transactional fixtures, and especially systems test
|
||||
|
@ -175,8 +183,11 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
# Disable the query cache within the block.
|
||||
def uncached(&)
|
||||
pool.disable_query_cache(&)
|
||||
#
|
||||
# Set <tt>dirties: false</tt> to prevent query caches on all connections from being cleared by write operations.
|
||||
# (By default, write operations dirty all connections' query caches in case they are replicas whose cache would now be outdated.)
|
||||
def uncached(dirties: true, &)
|
||||
pool.disable_query_cache(dirties: dirties, &)
|
||||
end
|
||||
|
||||
def disable_query_cache!
|
||||
|
|
|
@ -22,9 +22,12 @@ module ActiveRecord
|
|||
|
||||
# Disable the query cache within the block if Active Record is configured.
|
||||
# If it's not, it will execute the given block.
|
||||
def uncached(&block)
|
||||
#
|
||||
# Set <tt>dirties: false</tt> to prevent query caches on all connections from being cleared by write operations.
|
||||
# (By default, write operations dirty all connections' query caches in case they are replicas whose cache would now be outdated.)
|
||||
def uncached(dirties: true, &block)
|
||||
if connected? || !configurations.empty?
|
||||
connection_pool.disable_query_cache(&block)
|
||||
connection_pool.disable_query_cache(dirties: dirties, &block)
|
||||
else
|
||||
yield
|
||||
end
|
||||
|
|
|
@ -706,6 +706,57 @@ class QueryCacheTest < ActiveRecord::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
def test_query_cache_uncached_dirties
|
||||
mw = middleware { |env|
|
||||
Post.first
|
||||
assert_no_changes -> { ActiveRecord::Base.connection.query_cache.size } do
|
||||
Post.uncached(dirties: false) { Post.create!(title: "a new post", body: "and a body") }
|
||||
end
|
||||
|
||||
assert_changes -> { ActiveRecord::Base.connection.query_cache.size }, from: 1, to: 0 do
|
||||
Post.uncached(dirties: true) { Post.create!(title: "a new post", body: "and a body") }
|
||||
end
|
||||
}
|
||||
mw.call({})
|
||||
end
|
||||
|
||||
def test_query_cache_connection_uncached_dirties
|
||||
mw = middleware { |env|
|
||||
Post.first
|
||||
assert_no_changes -> { ActiveRecord::Base.connection.query_cache.size } do
|
||||
Post.connection.uncached(dirties: false) { Post.create!(title: "a new post", body: "and a body") }
|
||||
end
|
||||
|
||||
assert_changes -> { ActiveRecord::Base.connection.query_cache.size }, from: 1, to: 0 do
|
||||
Post.connection.uncached(dirties: true) { Post.create!(title: "a new post", body: "and a body") }
|
||||
end
|
||||
}
|
||||
mw.call({})
|
||||
end
|
||||
|
||||
def test_query_cache_uncached_dirties_disabled_with_nested_cache
|
||||
mw = middleware { |env|
|
||||
Post.first
|
||||
assert_changes -> { ActiveRecord::Base.connection.query_cache.size }, from: 1, to: 0 do
|
||||
Post.uncached(dirties: false) do
|
||||
Post.cache do
|
||||
Post.create!(title: "a new post", body: "and a body")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Post.first
|
||||
assert_changes -> { ActiveRecord::Base.connection.query_cache.size }, from: 1, to: 0 do
|
||||
Post.connection.uncached(dirties: false) do
|
||||
Post.connection.cache do
|
||||
Post.create!(title: "a new post", body: "and a body")
|
||||
end
|
||||
end
|
||||
end
|
||||
}
|
||||
mw.call({})
|
||||
end
|
||||
|
||||
private
|
||||
def with_temporary_connection_pool(&block)
|
||||
pool_config = ActiveRecord::Base.lease_connection.pool.pool_config
|
||||
|
|
Loading…
Reference in New Issue