mirror of
https://github.com/github/rails.git
synced 2026-01-29 00:08:15 -05:00
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:
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
80
activerecord/test/query_cache_test.rb
Normal file
80
activerecord/test/query_cache_test.rb
Normal 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
|
||||
Reference in New Issue
Block a user