diff options
author | Jeremy Kemper <jeremy@bitsweat.net> | 2007-09-17 06:15:58 +0000 |
---|---|---|
committer | Jeremy Kemper <jeremy@bitsweat.net> | 2007-09-17 06:15:58 +0000 |
commit | bfb906a905a1e8774e438b10e8cf703a829b55dc (patch) | |
tree | a24612f8ed74de3dec4519649488b817839f7dd5 /activerecord/lib | |
parent | 30fb7b8c8bfc72ed3097352539544c07cbb38d0d (diff) | |
download | rails-bfb906a905a1e8774e438b10e8cf703a829b55dc.tar.gz rails-bfb906a905a1e8774e438b10e8cf703a829b55dc.tar.bz2 rails-bfb906a905a1e8774e438b10e8cf703a829b55dc.zip |
Speed up and simplify query caching.
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@7498 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
Diffstat (limited to 'activerecord/lib')
12 files changed, 171 insertions, 175 deletions
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 8d032d7192..f1585d640b 100755 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -36,6 +36,7 @@ end require 'active_record/base' require 'active_record/observer' +require 'active_record/query_cache' require 'active_record/validations' require 'active_record/callbacks' require 'active_record/reflection' @@ -52,6 +53,7 @@ require 'active_record/xml_serialization' require 'active_record/attribute_methods' ActiveRecord::Base.class_eval do + extend ActiveRecord::QueryCache include ActiveRecord::Validations include ActiveRecord::Locking::Optimistic include ActiveRecord::Locking::Pessimistic diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index effb36faf1..4303d66451 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -10,14 +10,15 @@ module ActiveRecord # Returns a record hash with the column names as keys and column values # as values. def select_one(sql, name = nil) - result = select(sql, name) + result = select_all(sql, name) result.first if result end # Returns a single value from a record def select_value(sql, name = nil) - result = select_one(sql, name) - result.nil? ? nil : result.values.first + if result = select_one(sql, name) + result.values.first + end end # Returns an array of the values of the first column in a select: @@ -29,7 +30,9 @@ module ActiveRecord # Returns an array of arrays containing the field values. # Order is the same as that returned by #columns. - def select_rows(sql, name = nil) end + def select_rows(sql, name = nil) + raise NotImplementedError, "select_rows is an abstract method" + end # Executes the SQL statement in the context of this connection. def execute(sql, name = nil) @@ -38,17 +41,17 @@ module ActiveRecord # Returns the last auto-generated ID from the affected table. def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) - raise NotImplementedError, "insert is an abstract method" + insert_sql(sql, name, pk, id_value, sequence_name) end # Executes the update statement and returns the number of rows affected. def update(sql, name = nil) - execute(sql, name) + update_sql(sql, name) end # Executes the delete statement and returns the number of rows affected. def delete(sql, name = nil) - update(sql, name) + delete_sql(sql, name) end # Wrap a block in a transaction. Returns result of block. @@ -133,7 +136,7 @@ module ActiveRecord # Inserts the given fixture into the table. Overriden in adapters that require # something beyond a simple insert (eg. Oracle). def insert_fixture(fixture, table_name) - execute "INSERT INTO #{table_name} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert' + insert "INSERT INTO #{table_name} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert' end protected @@ -142,6 +145,22 @@ module ActiveRecord def select(sql, name = nil) raise NotImplementedError, "select is an abstract method" end + + # Returns the last auto-generated ID from the affected table. + def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) + execute(sql, name) + id_value + end + + # Executes the update statement and returns the number of rows affected. + def update_sql(sql, name = nil) + execute(sql, name) + end + + # Executes the delete statement and returns the number of rows affected. + def delete_sql(sql, name = nil) + update_sql(sql, name) + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb new file mode 100644 index 0000000000..42ff1d7d54 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -0,0 +1,90 @@ +module ActiveRecord + module ConnectionAdapters # :nodoc: + module QueryCache + class << self + def included(base) + base.class_eval do + attr_accessor :query_cache_enabled + alias_method_chain :columns, :query_cache + alias_method_chain :select_all, :query_cache + end + + dirties_query_cache base, :insert, :update, :delete + end + + def dirties_query_cache(base, *method_names) + method_names.each do |method_name| + base.class_eval <<-end_code, __FILE__, __LINE__ + def #{method_name}_with_query_dirty(*args) + clear_query_cache if @query_cache_enabled + #{method_name}_without_query_dirty(*args) + end + + alias_method_chain :#{method_name}, :query_dirty + end_code + end + end + end + + # Enable the query cache within the block. + def cache + old, @query_cache_enabled = @query_cache_enabled, true + @query_cache ||= {} + yield + ensure + clear_query_cache + @query_cache_enabled = old + end + + # Disable the query cache within the block. + def uncached + old, @query_cache_enabled = @query_cache_enabled, false + yield + ensure + @query_cache_enabled = old + end + + def clear_query_cache + @query_cache.clear if @query_cache + end + + def select_all_with_query_cache(*args) + if @query_cache_enabled + cache_sql(args.first) { select_all_without_query_cache(*args) } + else + select_all_without_query_cache(*args) + end + end + + def columns_with_query_cache(*args) + if @query_cache_enabled + @query_cache["SHOW FIELDS FROM #{args.first}"] ||= columns_without_query_cache(*args) + else + columns_without_query_cache(*args) + end + end + + private + def cache_sql(sql) + result = + if @query_cache.has_key?(sql) + log_info(sql, "CACHE", 0.0) + @query_cache[sql] + else + @query_cache[sql] = yield + end + + case result + when Array + result.collect { |row| row.dup } + when nil, Fixnum, Float, true, false + result + else + result.dup + end + rescue TypeError + result + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index fc29f11e15..8ca23ccdaf 100755 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -8,6 +8,7 @@ require 'active_record/connection_adapters/abstract/schema_statements' require 'active_record/connection_adapters/abstract/database_statements' require 'active_record/connection_adapters/abstract/quoting' require 'active_record/connection_adapters/abstract/connection_specification' +require 'active_record/connection_adapters/abstract/query_cache' module ActiveRecord module ConnectionAdapters # :nodoc: @@ -22,6 +23,7 @@ module ActiveRecord # SchemaStatements#remove_column are very useful. class AbstractAdapter include Quoting, DatabaseStatements, SchemaStatements + include QueryCache @@row_even = true def initialize(connection, logger = nil) #:nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/db2_adapter.rb b/activerecord/lib/active_record/connection_adapters/db2_adapter.rb index c496132fc0..b7c939d7d3 100644 --- a/activerecord/lib/active_record/connection_adapters/db2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/db2_adapter.rb @@ -63,9 +63,8 @@ begin rows end - def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) - execute(sql, name = nil) - id_value || last_insert_id + def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) + super || last_insert_id end def execute(sql, name = nil) diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 7cf8f478d7..20028ffcfd 100755 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -266,13 +266,13 @@ module ActiveRecord end end - def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc: - execute(sql, name = nil) + def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc: + super sql, name id_value || @connection.insert_id end - def update(sql, name = nil) #:nodoc: - execute(sql, name) + def update_sql(sql, name = nil) #:nodoc: + super @connection.affected_rows end diff --git a/activerecord/lib/active_record/connection_adapters/oracle_adapter.rb b/activerecord/lib/active_record/connection_adapters/oracle_adapter.rb index 58544f4cb2..99e22ca3f3 100644 --- a/activerecord/lib/active_record/connection_adapters/oracle_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/oracle_adapter.rb @@ -223,11 +223,6 @@ begin id end - def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc: - execute(sql, name) - id_value - end - def begin_db_transaction #:nodoc: @connection.autocommit = false end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index b14acc1e68..6a2b32a370 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -374,10 +374,9 @@ module ActiveRecord end # Executes an INSERT query and returns the new record's ID - def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) - execute(sql, name) + def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) table = sql.split(" ", 4)[2] - id_value || last_insert_id(table, sequence_name || default_sequence_name(table, pk)) + super || last_insert_id(table, sequence_name || default_sequence_name(table, pk)) end # Queries the database and returns the results in an Array or nil otherwise. @@ -404,8 +403,8 @@ module ActiveRecord end # Executes an UPDATE query and returns the number of affected tuples. - def update(sql, name = nil) - execute(sql, name).cmdtuples + def update_sql(sql, name = nil) + super.cmdtuples end # Begins a transaction. diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 57eefbf1d6..62d7e5364c 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -132,37 +132,18 @@ module ActiveRecord catch_schema_changes { log(sql, name) { @connection.execute(sql) } } end - def update(sql, name = nil) #:nodoc: - execute(sql, name) + def update_sql(sql, name = nil) #:nodoc: + super @connection.changes end - def delete(sql, name = nil) #:nodoc: + def delete_sql(sql, name = nil) #:nodoc: sql += " WHERE 1=1" unless sql =~ /WHERE/i - execute(sql, name) - @connection.changes + super sql, name end - def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc: - execute(sql, name = nil) - id_value || @connection.last_insert_row_id - end - - def select_all(sql, name = nil) #:nodoc: - execute(sql, name).map do |row| - record = {} - row.each_key do |key| - if key.is_a?(String) - record[key.sub(/^\w+\./, '')] = row[key] - end - end - record - end - end - - def select_one(sql, name = nil) #:nodoc: - result = select_all(sql, name) - result.nil? ? nil : result.first + def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc: + super || @connection.last_insert_row_id end def select_rows(sql, name = nil) @@ -267,6 +248,18 @@ module ActiveRecord protected + def select(sql, name = nil) #:nodoc: + execute(sql, name).map do |row| + record = {} + row.each_key do |key| + if key.is_a?(String) + record[key.sub(/^\w+\./, '')] = row[key] + end + end + record + end + end + def table_structure(table_name) returning structure = execute("PRAGMA table_info(#{table_name})") do raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty? diff --git a/activerecord/lib/active_record/connection_adapters/sqlserver_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlserver_adapter.rb index 4799c69b83..113e187766 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlserver_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlserver_adapter.rb @@ -314,18 +314,15 @@ module ActiveRecord columns end - def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) - execute(sql, name) - id_value || select_one("SELECT @@IDENTITY AS Ident")["Ident"] + def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) + super || select_value("SELECT @@IDENTITY AS Ident") end - def update(sql, name = nil) + def update_sql(sql, name = nil) execute(sql, name) do |handle| handle.rows - end || select_one("SELECT @@ROWCOUNT AS AffectedRows")["AffectedRows"] + end || select_value("SELECT @@ROWCOUNT AS AffectedRows") end - - alias_method :delete, :update def execute(sql, name = nil) if sql =~ /^\s*INSERT/i && (table_name = query_requires_identity_insert?(sql)) diff --git a/activerecord/lib/active_record/connection_adapters/sybase_adapter.rb b/activerecord/lib/active_record/connection_adapters/sybase_adapter.rb index c43e9435b4..d8ff88b287 100644 --- a/activerecord/lib/active_record/connection_adapters/sybase_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sybase_adapter.rb @@ -176,7 +176,7 @@ module ActiveRecord 30 end - def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) + def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) begin table_name = get_table_name(sql) col = get_identity_column(table_name) @@ -194,9 +194,7 @@ module ActiveRecord end log(sql, name) do - execute(sql, name) - ident = select_one("SELECT @@IDENTITY AS last_id")["last_id"] - id_value || ident + super || select_value("SELECT @@IDENTITY AS last_id") end ensure if ii_enabled @@ -219,7 +217,7 @@ module ActiveRecord def rollback_db_transaction() raw_execute "ROLLBACK TRAN" end def current_database - select_one("select DB_NAME() as name")["name"] + select_value("select DB_NAME() as name") end def tables(name = nil) diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb index 2cb9b97f8f..a8af89fcb9 100644 --- a/activerecord/lib/active_record/query_cache.rb +++ b/activerecord/lib/active_record/query_cache.rb @@ -1,119 +1,21 @@ module ActiveRecord - class QueryCache #:nodoc: - def initialize(connection) - @connection = connection - @query_cache = {} - end - - def clear_query_cache - @query_cache.clear - end - - def select_all(sql, name = nil) - cache(sql) { @connection.select_all(sql, name) } - end - - def select_one(sql, name = nil) - cache(sql) { @connection.select_one(sql, name) } - end - - def select_values(sql, name = nil) - cache(sql) { @connection.select_values(sql, name) } - end - - def select_value(sql, name = nil) - 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, sequence_name = nil) - clear_query_cache - @connection.insert(sql, name, pk, id_value, sequence_name) - end - - def update(sql, name = nil) - clear_query_cache - @connection.update(sql, name) - end - - def delete(sql, name = nil) - clear_query_cache - @connection.delete(sql, name) - end - - private - def cache(sql) - result = if @query_cache.has_key?(sql) - log_info(sql, "CACHE", 0.0) - @query_cache[sql] - else - @query_cache[sql] = yield - end - - case result - when Array - result.collect { |row| row.dup } - when nil, Fixnum, Float, true, false - result - else - result.dup - end - rescue TypeError - result - end - - def method_missing(method, *arguments, &proc) - @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 query_caches - Thread.current["query_cache_#{connection_without_query_cache.object_id}"] ||= {} - end - - def query_cache - if query_caches[self] - query_caches[self] - elsif superclass.respond_to?(:query_cache) and superclass.respond_to?(:connection) and superclass.connection_without_query_cache == connection_without_query_cache - superclass.query_cache - end - end - - def query_cache=(cache) - query_caches[self] = cache - end - - # Use a query cache within the given block. - def cache - # Don't cache if Active Record is not configured. - if ActiveRecord::Base.configurations.blank? - yield - else - begin - self.query_cache = QueryCache.new(connection_without_query_cache) - yield - ensure - self.query_cache = nil - end - end + module QueryCache + # Enable the query cache within the block if Active Record is configured. + def cache(&block) + if ActiveRecord::Base.configurations.blank? + yield + else + connection.cache(&block) end + end - def connection - query_cache || connection_without_query_cache + # Disable the query cache within the block if Active Record is configured. + def uncached(&block) + if ActiveRecord::Base.configurations.blank? + yield + else + connection.uncached(&block) end end - end + end end |