Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Changeset 7498

Show
Ignore:
Timestamp:
09/17/07 06:15:58 (10 months ago)
Author:
bitsweat
Message:

Speed up and simplify query caching.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/actionpack/lib/action_controller/caching.rb

    r7475 r7498  
    1212  module Caching 
    1313    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       end 
    19  
    2014      base.class_eval do 
     15        include Pages, Actions, Fragments 
     16 
     17        if defined? ActiveRecord 
     18          include Sweeping, SqlCache 
     19        end 
     20 
    2121        @@perform_caching = true 
    2222        cattr_accessor :perform_caching 
  • trunk/activerecord/CHANGELOG

    r7497 r7498  
    11*SVN* 
     2 
     3* Speed up and simplify query caching.  [Jeremy Kemper] 
    24 
    35* connection.select_rows 'sql' returns an array (rows) of arrays (field values).  #2329 [Michael Schuerig] 
  • trunk/activerecord/lib/active_record.rb

    r7454 r7498  
    3737require 'active_record/base' 
    3838require 'active_record/observer' 
     39require 'active_record/query_cache' 
    3940require 'active_record/validations' 
    4041require 'active_record/callbacks' 
     
    5354 
    5455ActiveRecord::Base.class_eval do 
     56  extend ActiveRecord::QueryCache 
    5557  include ActiveRecord::Validations 
    5658  include ActiveRecord::Locking::Optimistic 
  • trunk/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb

    r6375 r7498  
    99require 'active_record/connection_adapters/abstract/quoting' 
    1010require 'active_record/connection_adapters/abstract/connection_specification' 
     11require 'active_record/connection_adapters/abstract/query_cache' 
    1112 
    1213module ActiveRecord 
     
    2324    class AbstractAdapter 
    2425      include Quoting, DatabaseStatements, SchemaStatements 
     26      include QueryCache 
    2527      @@row_even = true 
    2628 
  • trunk/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb

    r7497 r7498  
    1111      # as values. 
    1212      def select_one(sql, name = nil) 
    13         result = select(sql, name) 
     13        result = select_all(sql, name) 
    1414        result.first if result 
    1515      end 
     
    1717      # Returns a single value from a record 
    1818      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 
    2122      end 
    2223 
     
    3031      # Returns an array of arrays containing the field values. 
    3132      # 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 
    3336 
    3437      # Executes the SQL statement in the context of this connection. 
     
    3942      # Returns the last auto-generated ID from the affected table. 
    4043      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) 
    4245      end 
    4346 
    4447      # Executes the update statement and returns the number of rows affected. 
    4548      def update(sql, name = nil) 
    46         execute(sql, name) 
     49        update_sql(sql, name) 
    4750      end 
    4851 
    4952      # Executes the delete statement and returns the number of rows affected. 
    5053      def delete(sql, name = nil) 
    51         update(sql, name) 
     54        delete_sql(sql, name) 
    5255      end 
    5356 
     
    134137      # something beyond a simple insert (eg. Oracle). 
    135138      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' 
    137140      end 
    138141 
     
    143146          raise NotImplementedError, "select is an abstract method" 
    144147        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 
    145164    end 
    146165  end 
  • trunk/activerecord/lib/active_record/connection_adapters/db2_adapter.rb

    r7497 r7498  
    6464        end 
    6565 
    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 
    6968        end 
    7069 
  • trunk/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb

    r7497 r7498  
    267267      end 
    268268 
    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 
    271271        id_value || @connection.insert_id 
    272272      end 
    273273 
    274       def update(sql, name = nil) #:nodoc: 
    275         execute(sql, name) 
     274      def update_sql(sql, name = nil) #:nodoc: 
     275        super 
    276276        @connection.affected_rows 
    277277      end 
  • trunk/activerecord/lib/active_record/connection_adapters/oracle_adapter.rb

    r7471 r7498  
    224224        end 
    225225 
    226         def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc: 
    227           execute(sql, name) 
    228           id_value 
    229         end 
    230  
    231226        def begin_db_transaction #:nodoc: 
    232227          @connection.autocommit = false 
  • trunk/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb

    r7497 r7498  
    375375 
    376376      # 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) 
    379378        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)) 
    381380      end 
    382381 
     
    405404 
    406405      # Executes an UPDATE query and returns the number of affected tuples. 
    407       def update(sql, name = nil) 
    408         execute(sql, name).cmdtuples 
     406      def update_sql(sql, name = nil) 
     407        super.cmdtuples 
    409408      end 
    410409 
  • trunk/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb

    r7497 r7498  
    133133      end 
    134134 
    135       def update(sql, name = nil) #:nodoc: 
    136         execute(sql, name) 
     135      def update_sql(sql, name = nil) #:nodoc: 
     136        super 
    137137        @connection.changes 
    138138      end 
    139139 
    140       def delete(sql, name = nil) #:nodoc: 
     140      def delete_sql(sql, name = nil) #:nodoc: 
    141141        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 
    166147      end 
    167148 
     
    268249 
    269250      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 
    270263        def table_structure(table_name) 
    271264          returning structure = execute("PRAGMA table_info(#{table_name})") do 
  • trunk/activerecord/lib/active_record/connection_adapters/sqlserver_adapter.rb

    r7497 r7498  
    315315      end 
    316316 
    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) 
    323322        execute(sql, name) do |handle| 
    324323          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 
    329326 
    330327      def execute(sql, name = nil) 
  • trunk/activerecord/lib/active_record/connection_adapters/sybase_adapter.rb

    r6349 r7498  
    177177      end 
    178178 
    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) 
    180180        begin 
    181181          table_name = get_table_name(sql) 
     
    195195 
    196196          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") 
    200198          end 
    201199        ensure 
     
    220218 
    221219      def current_database 
    222         select_one("select DB_NAME() as name")["name"] 
     220        select_value("select DB_NAME() as name") 
    223221      end 
    224222 
  • trunk/activerecord/lib/active_record/query_cache.rb

    r7420 r7498  
    11module 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 
    610    end 
    711 
    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) 
    11618      end 
    11719    end 
    118   end   
     20  end 
    11921end 
  • trunk/activerecord/test/query_cache_test.rb

    r7419 r7498  
    55require 'fixtures/course' 
    66 
    7 require 'active_record/query_cache' 
    87 
    98class QueryCacheTest < Test::Unit::TestCase 
    10   fixtures :tasks 
    11    
     9  fixtures :tasks, :topics 
     10 
    1211  def test_find_queries 
    13     assert_queries(2) { Task.find(1); Task.find(1) } 
     12    assert_queries(2) { Task.find(1); Task.find(1) } 
    1413  end 
    1514 
    1615  def test_find_queries_with_cache 
    1716    Task.cache do 
    18       assert_queries(1) { Task.find(1); Task.find(1) } 
     17      assert_queries(1) { Task.find(1); Task.find(1) } 
    1918    end 
    2019  end 
     
    2221  def test_count_queries_with_cache 
    2322    Task.cache do 
    24       assert_queries(1) { Task.count; Task.count } 
     23      assert_queries(1) { Task.count; Task.count } 
    2524    end 
    26   end 
    27  
    28   def test_query_cache_returned 
    29     assert_not_equal ActiveRecord::QueryCache, Task.connection.class 
    30     Task.cache do 
    31       assert_equal ActiveRecord::QueryCache, Task.connection.class 
    32     end     
    3325  end 
    3426 
     
    4436  end 
    4537 
    46   def test_cache_is_scoped_on_actual_class_only 
     38  def test_cache_is_flat 
    4739    Task.cache do 
    4840      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); } 
    5042    end 
    51   end 
    52    
    53   def test_cache_is_scoped_on_all_descending_classes 
     43 
    5444    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) } 
    6646    end 
    6747  end 
    6848 
    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 
    7454end 
    7555 
    76 uses_mocha('QueryCacheExpiryTest') do 
     56uses_mocha 'QueryCacheExpiryTest' do 
    7757 
    7858class QueryCacheExpiryTest < Test::Unit::TestCase 
     
    8060 
    8161  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 
    8567      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 
    8675    end 
     76    assert !Task.connection.query_cache_enabled 
    8777  end 
    8878 
    89   def test_sav
    90     ActiveRecord::QueryCache.any_instance.expects(:clear_query_cache).times(1
    91      
    92     Task.cache do  
    93       Task.find(1).save 
     79  def test_updat
     80    Task.connection.expects(:clear_query_cache).times(2
     81 
     82    Task.cache do 
     83      Task.find(1).save! 
    9484    end 
    9585  end 
    9686 
    9787  def test_destroy 
    98     ActiveRecord::QueryCache.any_instance.expects(:clear_query_cache).at_least_once 
    99      
    100     Task.cache do  
     88    Task.connection.expects(:clear_query_cache).times(2) 
     89 
     90    Task.cache do 
    10191      Task.find(1).destroy 
    10292    end 
    10393  end 
    10494 
    105   def test_create 
    106     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 
    10999      Task.create! 
    110     end 
    111   end 
    112  
    113   def test_new_save 
    114     ActiveRecord::QueryCache.any_instance.expects(:clear_query_cache).times(1) 
    115      
    116     Task.cache do  
    117       Task.new.save 
    118100    end 
    119101  end