Introducing Model.cache { ... } for the occasional query caching needs. ( fantastic to reduce the 200 SELECT * from accounts WHERE id=1 queries in your views )

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@6138 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
Tobias Lütke
2007-02-06 21:16:07 +00:00
parent 23b2abe313
commit f458b376c5
4 changed files with 136 additions and 34 deletions

View File

@@ -1,5 +1,8 @@
*SVN*
* Reworked David's query cache to be available as Model.cache {...}. For the duration of the block no select query should be run more then once. Any inserts/deletes/executes will flush the whole cache however [Tobias Luetke]
Task.cache { Task.find(1); Task.find(1) } #=> 1 query
* When dealing with SQLite3, use the table_info pragma helper, so that the bindings can do some translation for when sqlite3 breaks incompatibly between point releases. [Jamis Buck]
* Oracle: fix lob and text default handling. #7344 [gfriedrich, Michael Schoen]

View File

@@ -6,7 +6,7 @@ module ActiveRecord
end
def clear_query_cache
@query_cache = {}
@query_cache.clear
end
def select_all(sql, name = nil)
@@ -16,14 +16,27 @@ module ActiveRecord
def select_one(sql, name = nil)
@query_cache[sql] ||= @connection.select_one(sql, name)
end
def select_values(sql, name = nil)
(@query_cache[sql] ||= @connection.select_values(sql, name)).dup
end
def select_value(sql, name = nil)
@query_cache[sql] ||= @connection.select_value(sql, name)
end
def execute(sql, name = nil)
clear_query_cache
@connection.execute(sql, name)
end
def columns(table_name, name = nil)
@query_cache["SHOW FIELDS FROM #{table_name}"] ||= @connection.columns(table_name, name)
end
def insert(sql, name = nil, pk = nil, id_value = nil)
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
clear_query_cache
@connection.insert(sql, name, pk, id_value)
@connection.insert(sql, name, pk, id_value, sequence_name)
end
def update(sql, name = nil)
@@ -41,24 +54,26 @@ module ActiveRecord
@connection.send(method, *arguments, &proc)
end
end
class Base
# Set the connection for the class with caching on
class << self
alias_method :connection_without_query_cache=, :connection=
def connection=(spec)
if spec.is_a?(ConnectionSpecification) and spec.config[:query_cache]
spec = QueryCache.new(self.send(spec.adapter_method, spec.config))
end
self.connection_without_query_cache = spec
alias_method :connection_without_query_cache, :connection
def query_caches
(Thread.current[:query_cache] ||= {})
end
def cache
query_caches[self] = QueryCache.new(connection)
yield
ensure
query_caches[self] = nil
end
def connection
query_caches[self] || connection_without_query_cache
end
end
end
class AbstractAdapter #:nodoc:
# Stub method to be able to treat the connection the same whether the query cache has been turned on or not
def clear_query_cache
end
end
end
end

View File

@@ -36,16 +36,10 @@ class Test::Unit::TestCase #:nodoc:
end
def assert_queries(num = 1)
ActiveRecord::Base.connection.class.class_eval do
self.query_count = 0
alias_method :execute, :execute_with_query_counting
end
$query_count = 0
yield
ensure
ActiveRecord::Base.connection.class.class_eval do
alias_method :execute, :execute_without_query_counting
end
assert_equal num, ActiveRecord::Base.connection.query_count, "#{ActiveRecord::Base.connection.query_count} instead of #{num} queries were executed."
assert_equal num, $query_count, "#{$query_count} instead of #{num} queries were executed."
end
def assert_no_queries(&block)
@@ -60,16 +54,26 @@ def current_adapter?(*types)
end
end
ActiveRecord::Base.connection.class.class_eval do
cattr_accessor :query_count
def uses_mocha(test_name)
require 'mocha'
require 'stubba'
yield
rescue LoadError
$stderr.puts "Skipping #{test_name} tests. `gem install mocha` and try again."
end
# Array of regexes of queries that are not counted against query_count
@@ignore_list = [/^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/]
ActiveRecord::Base.connection.class.class_eval do
if not (const_get('IGNORED_SQL') rescue nil)
IGNORED_SQL = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/]
alias_method :execute_without_query_counting, :execute
def execute_with_query_counting(sql, name = nil, &block)
self.query_count += 1 unless @@ignore_list.any? { |r| sql =~ r }
execute_without_query_counting(sql, name, &block)
def execute_with_counting(sql, name = nil, &block)
$query_count ||= 0
$query_count += 1 unless IGNORED_SQL.any? { |r| sql =~ r }
execute_without_counting(sql, name, &block)
end
alias_method_chain :execute, :counting
end
end

View File

@@ -0,0 +1,80 @@
require 'abstract_unit'
require 'fixtures/topic'
require 'fixtures/reply'
require 'fixtures/task'
class QueryCacheTest < Test::Unit::TestCase
fixtures :tasks
def test_find_queries
assert_queries(2) { Task.find(1); Task.find(1) }
end
def test_find_queries_with_cache
Task.cache do
assert_queries(1) { Task.find(1); Task.find(1) }
end
end
def test_find_queries_with_cache
Task.cache do
assert_queries(1) { Task.find(1); Task.find(1) }
end
end
def test_cache_is_scoped_on_actual_class_only
Task.cache do
assert_queries(2) { Topic.find(1); Topic.find(1) }
end
end
end
uses_mocha('QueryCacheExpiryTest') do
class QueryCacheExpiryTest < Test::Unit::TestCase
fixtures :tasks
def test_find
ActiveRecord::QueryCache.any_instance.expects(:clear_query_cache).times(0)
Task.cache do
Task.find(1)
end
end
def test_save
ActiveRecord::QueryCache.any_instance.expects(:clear_query_cache).times(1)
Task.cache do
Task.find(1).save
end
end
def test_destroy
ActiveRecord::QueryCache.any_instance.expects(:clear_query_cache).at_least_once
Task.cache do
Task.find(1).destroy
end
end
def test_create
ActiveRecord::QueryCache.any_instance.expects(:clear_query_cache).times(1)
Task.cache do
Task.create!
end
end
def test_new_save
ActiveRecord::QueryCache.any_instance.expects(:clear_query_cache).times(1)
Task.cache do
Task.new.save
end
end
end
end