Changeset 7498
- Timestamp:
- 09/17/07 06:15:58 (10 months ago)
- Files:
-
- trunk/actionpack/lib/action_controller/caching.rb (modified) (1 diff)
- trunk/activerecord/CHANGELOG (modified) (1 diff)
- trunk/activerecord/lib/active_record.rb (modified) (2 diffs)
- trunk/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb (modified) (2 diffs)
- trunk/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb (modified) (6 diffs)
- trunk/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb (added)
- trunk/activerecord/lib/active_record/connection_adapters/db2_adapter.rb (modified) (1 diff)
- trunk/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb (modified) (1 diff)
- trunk/activerecord/lib/active_record/connection_adapters/oracle_adapter.rb (modified) (1 diff)
- trunk/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb (modified) (2 diffs)
- trunk/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb (modified) (2 diffs)
- trunk/activerecord/lib/active_record/connection_adapters/sqlserver_adapter.rb (modified) (1 diff)
- trunk/activerecord/lib/active_record/connection_adapters/sybase_adapter.rb (modified) (3 diffs)
- trunk/activerecord/lib/active_record/query_cache.rb (modified) (1 diff)
- trunk/activerecord/test/query_cache_test.rb (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/actionpack/lib/action_controller/caching.rb
r7475 r7498 12 12 module Caching 13 13 def self.included(base) #:nodoc: 14 base.send(:include, Pages, Actions, Fragments)15 if defined?(ActiveRecord)16 require 'active_record/query_cache'17 base.send(:include, Sweeping, SqlCache)18 end19 20 14 base.class_eval do 15 include Pages, Actions, Fragments 16 17 if defined? ActiveRecord 18 include Sweeping, SqlCache 19 end 20 21 21 @@perform_caching = true 22 22 cattr_accessor :perform_caching trunk/activerecord/CHANGELOG
r7497 r7498 1 1 *SVN* 2 3 * Speed up and simplify query caching. [Jeremy Kemper] 2 4 3 5 * connection.select_rows 'sql' returns an array (rows) of arrays (field values). #2329 [Michael Schuerig] trunk/activerecord/lib/active_record.rb
r7454 r7498 37 37 require 'active_record/base' 38 38 require 'active_record/observer' 39 require 'active_record/query_cache' 39 40 require 'active_record/validations' 40 41 require 'active_record/callbacks' … … 53 54 54 55 ActiveRecord::Base.class_eval do 56 extend ActiveRecord::QueryCache 55 57 include ActiveRecord::Validations 56 58 include ActiveRecord::Locking::Optimistic trunk/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
r6375 r7498 9 9 require 'active_record/connection_adapters/abstract/quoting' 10 10 require 'active_record/connection_adapters/abstract/connection_specification' 11 require 'active_record/connection_adapters/abstract/query_cache' 11 12 12 13 module ActiveRecord … … 23 24 class AbstractAdapter 24 25 include Quoting, DatabaseStatements, SchemaStatements 26 include QueryCache 25 27 @@row_even = true 26 28 trunk/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
r7497 r7498 11 11 # as values. 12 12 def select_one(sql, name = nil) 13 result = select (sql, name)13 result = select_all(sql, name) 14 14 result.first if result 15 15 end … … 17 17 # Returns a single value from a record 18 18 def select_value(sql, name = nil) 19 result = select_one(sql, name) 20 result.nil? ? nil : result.values.first 19 if result = select_one(sql, name) 20 result.values.first 21 end 21 22 end 22 23 … … 30 31 # Returns an array of arrays containing the field values. 31 32 # Order is the same as that returned by #columns. 32 def select_rows(sql, name = nil) end 33 def select_rows(sql, name = nil) 34 raise NotImplementedError, "select_rows is an abstract method" 35 end 33 36 34 37 # Executes the SQL statement in the context of this connection. … … 39 42 # Returns the last auto-generated ID from the affected table. 40 43 def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) 41 raise NotImplementedError, "insert is an abstract method"44 insert_sql(sql, name, pk, id_value, sequence_name) 42 45 end 43 46 44 47 # Executes the update statement and returns the number of rows affected. 45 48 def update(sql, name = nil) 46 execute(sql, name)49 update_sql(sql, name) 47 50 end 48 51 49 52 # Executes the delete statement and returns the number of rows affected. 50 53 def delete(sql, name = nil) 51 update(sql, name)54 delete_sql(sql, name) 52 55 end 53 56 … … 134 137 # something beyond a simple insert (eg. Oracle). 135 138 def insert_fixture(fixture, table_name) 136 execute"INSERT INTO #{table_name} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert'139 insert "INSERT INTO #{table_name} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert' 137 140 end 138 141 … … 143 146 raise NotImplementedError, "select is an abstract method" 144 147 end 148 149 # Returns the last auto-generated ID from the affected table. 150 def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) 151 execute(sql, name) 152 id_value 153 end 154 155 # Executes the update statement and returns the number of rows affected. 156 def update_sql(sql, name = nil) 157 execute(sql, name) 158 end 159 160 # Executes the delete statement and returns the number of rows affected. 161 def delete_sql(sql, name = nil) 162 update_sql(sql, name) 163 end 145 164 end 146 165 end trunk/activerecord/lib/active_record/connection_adapters/db2_adapter.rb
r7497 r7498 64 64 end 65 65 66 def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) 67 execute(sql, name = nil) 68 id_value || last_insert_id 66 def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) 67 super || last_insert_id 69 68 end 70 69 trunk/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
r7497 r7498 267 267 end 268 268 269 def insert (sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:270 execute(sql, name = nil)269 def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc: 270 super sql, name 271 271 id_value || @connection.insert_id 272 272 end 273 273 274 def update (sql, name = nil) #:nodoc:275 execute(sql, name)274 def update_sql(sql, name = nil) #:nodoc: 275 super 276 276 @connection.affected_rows 277 277 end trunk/activerecord/lib/active_record/connection_adapters/oracle_adapter.rb
r7471 r7498 224 224 end 225 225 226 def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:227 execute(sql, name)228 id_value229 end230 231 226 def begin_db_transaction #:nodoc: 232 227 @connection.autocommit = false trunk/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
r7497 r7498 375 375 376 376 # Executes an INSERT query and returns the new record's ID 377 def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) 378 execute(sql, name) 377 def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) 379 378 table = sql.split(" ", 4)[2] 380 id_value|| last_insert_id(table, sequence_name || default_sequence_name(table, pk))379 super || last_insert_id(table, sequence_name || default_sequence_name(table, pk)) 381 380 end 382 381 … … 405 404 406 405 # Executes an UPDATE query and returns the number of affected tuples. 407 def update (sql, name = nil)408 execute(sql, name).cmdtuples406 def update_sql(sql, name = nil) 407 super.cmdtuples 409 408 end 410 409 trunk/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
r7497 r7498 133 133 end 134 134 135 def update (sql, name = nil) #:nodoc:136 execute(sql, name)135 def update_sql(sql, name = nil) #:nodoc: 136 super 137 137 @connection.changes 138 138 end 139 139 140 def delete (sql, name = nil) #:nodoc:140 def delete_sql(sql, name = nil) #:nodoc: 141 141 sql += " WHERE 1=1" unless sql =~ /WHERE/i 142 execute(sql, name) 143 @connection.changes 144 end 145 146 def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc: 147 execute(sql, name = nil) 148 id_value || @connection.last_insert_row_id 149 end 150 151 def select_all(sql, name = nil) #:nodoc: 152 execute(sql, name).map do |row| 153 record = {} 154 row.each_key do |key| 155 if key.is_a?(String) 156 record[key.sub(/^\w+\./, '')] = row[key] 157 end 158 end 159 record 160 end 161 end 162 163 def select_one(sql, name = nil) #:nodoc: 164 result = select_all(sql, name) 165 result.nil? ? nil : result.first 142 super sql, name 143 end 144 145 def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc: 146 super || @connection.last_insert_row_id 166 147 end 167 148 … … 268 249 269 250 protected 251 def select(sql, name = nil) #:nodoc: 252 execute(sql, name).map do |row| 253 record = {} 254 row.each_key do |key| 255 if key.is_a?(String) 256 record[key.sub(/^\w+\./, '')] = row[key] 257 end 258 end 259 record 260 end 261 end 262 270 263 def table_structure(table_name) 271 264 returning structure = execute("PRAGMA table_info(#{table_name})") do trunk/activerecord/lib/active_record/connection_adapters/sqlserver_adapter.rb
r7497 r7498 315 315 end 316 316 317 def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) 318 execute(sql, name) 319 id_value || select_one("SELECT @@IDENTITY AS Ident")["Ident"] 320 end 321 322 def update(sql, name = nil) 317 def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) 318 super || select_value("SELECT @@IDENTITY AS Ident") 319 end 320 321 def update_sql(sql, name = nil) 323 322 execute(sql, name) do |handle| 324 323 handle.rows 325 end || select_one("SELECT @@ROWCOUNT AS AffectedRows")["AffectedRows"] 326 end 327 328 alias_method :delete, :update 324 end || select_value("SELECT @@ROWCOUNT AS AffectedRows") 325 end 329 326 330 327 def execute(sql, name = nil) trunk/activerecord/lib/active_record/connection_adapters/sybase_adapter.rb
r6349 r7498 177 177 end 178 178 179 def insert (sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)179 def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) 180 180 begin 181 181 table_name = get_table_name(sql) … … 195 195 196 196 log(sql, name) do 197 execute(sql, name) 198 ident = select_one("SELECT @@IDENTITY AS last_id")["last_id"] 199 id_value || ident 197 super || select_value("SELECT @@IDENTITY AS last_id") 200 198 end 201 199 ensure … … 220 218 221 219 def current_database 222 select_ one("select DB_NAME() as name")["name"]220 select_value("select DB_NAME() as name") 223 221 end 224 222 trunk/activerecord/lib/active_record/query_cache.rb
r7420 r7498 1 1 module ActiveRecord 2 class QueryCache #:nodoc: 3 def initialize(connection) 4 @connection = connection 5 @query_cache = {} 2 module QueryCache 3 # Enable the query cache within the block if Active Record is configured. 4 def cache(&block) 5 if ActiveRecord::Base.configurations.blank? 6 yield 7 else 8 connection.cache(&block) 9 end 6 10 end 7 11 8 def clear_query_cache 9 @query_cache.clear 10 end 11 12 def select_all(sql, name = nil) 13 cache(sql) { @connection.select_all(sql, name) } 14 end 15 16 def select_one(sql, name = nil) 17 cache(sql) { @connection.select_one(sql, name) } 18 end 19 20 def select_values(sql, name = nil) 21 cache(sql) { @connection.select_values(sql, name) } 22 end 23 24 def select_value(sql, name = nil) 25 cache(sql) { @connection.select_value(sql, name) } 26 end 27 28 def execute(sql, name = nil) 29 clear_query_cache 30 @connection.execute(sql, name) 31 end 32 33 def columns(table_name, name = nil) 34 @query_cache["SHOW FIELDS FROM #{table_name}"] ||= @connection.columns(table_name, name) 35 end 36 37 def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) 38 clear_query_cache 39 @connection.insert(sql, name, pk, id_value, sequence_name) 40 end 41 42 def update(sql, name = nil) 43 clear_query_cache 44 @connection.update(sql, name) 45 end 46 47 def delete(sql, name = nil) 48 clear_query_cache 49 @connection.delete(sql, name) 50 end 51 52 private 53 def cache(sql) 54 result = if @query_cache.has_key?(sql) 55 log_info(sql, "CACHE", 0.0) 56 @query_cache[sql] 57 else 58 @query_cache[sql] = yield 59 end 60 61 case result 62 when Array 63 result.collect { |row| row.dup } 64 when nil, Fixnum, Float, true, false 65 result 66 else 67 result.dup 68 end 69 rescue TypeError 70 result 71 end 72 73 def method_missing(method, *arguments, &proc) 74 @connection.send(method, *arguments, &proc) 75 end 76 end 77 78 class Base 79 # Set the connection for the class with caching on 80 class << self 81 alias_method :connection_without_query_cache, :connection 82 83 def query_caches 84 Thread.current["query_cache_#{connection_without_query_cache.object_id}"] ||= {} 85 end 86 87 def query_cache 88 if query_caches[self] 89 query_caches[self] 90 elsif superclass.respond_to?(:query_cache) and superclass.respond_to?(:connection) and superclass.connection_without_query_cache == connection_without_query_cache 91 superclass.query_cache 92 end 93 end 94 95 def query_cache=(cache) 96 query_caches[self] = cache 97 end 98 99 # Use a query cache within the given block. 100 def cache 101 # Don't cache if Active Record is not configured. 102 if ActiveRecord::Base.configurations.blank? 103 yield 104 else 105 begin 106 self.query_cache = QueryCache.new(connection_without_query_cache) 107 yield 108 ensure 109 self.query_cache = nil 110 end 111 end 112 end 113 114 def connection 115 query_cache || connection_without_query_cache 12 # Disable the query cache within the block if Active Record is configured. 13 def uncached(&block) 14 if ActiveRecord::Base.configurations.blank? 15 yield 16 else 17 connection.uncached(&block) 116 18 end 117 19 end 118 end 20 end 119 21 end trunk/activerecord/test/query_cache_test.rb
r7419 r7498 5 5 require 'fixtures/course' 6 6 7 require 'active_record/query_cache'8 7 9 8 class QueryCacheTest < Test::Unit::TestCase 10 fixtures :tasks 11 9 fixtures :tasks, :topics 10 12 11 def test_find_queries 13 assert_queries(2) { Task.find(1); Task.find(1) }12 assert_queries(2) { Task.find(1); Task.find(1) } 14 13 end 15 14 16 15 def test_find_queries_with_cache 17 16 Task.cache do 18 assert_queries(1) { Task.find(1); Task.find(1) }17 assert_queries(1) { Task.find(1); Task.find(1) } 19 18 end 20 19 end … … 22 21 def test_count_queries_with_cache 23 22 Task.cache do 24 assert_queries(1) { Task.count; Task.count }23 assert_queries(1) { Task.count; Task.count } 25 24 end 26 end27 28 def test_query_cache_returned29 assert_not_equal ActiveRecord::QueryCache, Task.connection.class30 Task.cache do31 assert_equal ActiveRecord::QueryCache, Task.connection.class32 end33 25 end 34 26 … … 44 36 end 45 37 46 def test_cache_is_ scoped_on_actual_class_only38 def test_cache_is_flat 47 39 Task.cache do 48 40 Topic.columns # don't count this query 49 assert_queries( 2) {Topic.find(1); Topic.find(1); }41 assert_queries(1) { Topic.find(1); Topic.find(1); } 50 42 end 51 end 52 53 def test_cache_is_scoped_on_all_descending_classes 43 54 44 ActiveRecord::Base.cache do 55 assert_queries(1) { Task.find(1); Task.find(1) } 56 end 57 end 58 59 def test_cache_does_not_blow_up_other_connections 60 assert_not_equal Course.connection.object_id, Task.connection.object_id, 61 "Connections should be different, Course connects to a different database" 62 63 ActiveRecord::Base.cache do 64 assert_not_equal Course.connection.object_id, Task.connection.object_id, 65 "Connections should be different, Course connects to a different database" 45 assert_queries(1) { Task.find(1); Task.find(1) } 66 46 end 67 47 end 68 48 69 def test_cache_does_not_wrap_string_results_in_arrays 70 Task.cache do 71 assert_instance_of String, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks") 72 end 73 end 49 def test_cache_does_not_wrap_string_results_in_arrays 50 Task.cache do 51 assert_instance_of String, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks") 52 end 53 end 74 54 end 75 55 76 uses_mocha ('QueryCacheExpiryTest')do56 uses_mocha 'QueryCacheExpiryTest' do 77 57 78 58 class QueryCacheExpiryTest < Test::Unit::TestCase … … 80 60 81 61 def test_find 82 ActiveRecord::QueryCache.any_instance.expects(:clear_query_cache).times(0) 83 84 Task.cache do 62 Task.connection.expects(:clear_query_cache).times(1) 63 64 assert !Task.connection.query_cache_enabled 65 Task.cache do 66 assert Task.connection.query_cache_enabled 85 67 Task.find(1) 68 69 Task.uncached do 70 assert !Task.connection.query_cache_enabled 71 Task.find(1) 72 end 73 74 assert Task.connection.query_cache_enabled 86 75 end 76 assert !Task.connection.query_cache_enabled 87 77 end 88 78 89 def test_ save90 ActiveRecord::QueryCache.any_instance.expects(:clear_query_cache).times(1)91 92 Task.cache do 93 Task.find(1).save 79 def test_update 80 Task.connection.expects(:clear_query_cache).times(2) 81 82 Task.cache do 83 Task.find(1).save! 94 84 end 95 85 end 96 86 97 87 def test_destroy 98 ActiveRecord::QueryCache.any_instance.expects(:clear_query_cache).at_least_once99 100 Task.cache do 88 Task.connection.expects(:clear_query_cache).times(2) 89 90 Task.cache do 101 91 Task.find(1).destroy 102 92 end 103 93 end 104 94 105 def test_ create106 ActiveRecord:: QueryCache.any_instance.expects(:clear_query_cache).times(1)107 108 Task.cache do 95 def test_insert 96 ActiveRecord::Base.connection.expects(:clear_query_cache).times(2) 97 98 Task.cache do 109 99 Task.create! 110 end111 end112 113 def test_new_save114 ActiveRecord::QueryCache.any_instance.expects(:clear_query_cache).times(1)115 116 Task.cache do117 Task.new.save118 100 end 119 101 end