diff options
Diffstat (limited to 'activerecord/lib/active_record/statement_cache.rb')
-rw-r--r-- | activerecord/lib/active_record/statement_cache.rb | 97 |
1 files changed, 92 insertions, 5 deletions
diff --git a/activerecord/lib/active_record/statement_cache.rb b/activerecord/lib/active_record/statement_cache.rb index dd4ee0c4a0..6090dfeb13 100644 --- a/activerecord/lib/active_record/statement_cache.rb +++ b/activerecord/lib/active_record/statement_cache.rb @@ -14,13 +14,100 @@ module ActiveRecord # The relation returned by the block is cached, and for each +execute+ call the cached relation gets duped. # Database is queried when +to_a+ is called on the relation. class StatementCache - def initialize - @relation = yield - raise ArgumentError.new("Statement cannot be nil") if @relation.nil? + Substitute = Struct.new :name + + class Query + def initialize(sql) + @sql = sql + end + + def sql_for(binds, connection) + @sql + end + end + + class PartialQuery < Query + def sql_for(binds, connection) + @sql.gsub(/\?/) { connection.quote(*binds.shift.reverse) } + end + end + + def self.query(visitor, ast) + Query.new visitor.accept(ast) + end + + def self.partial_query(visitor, ast) + sql = visitor.accept(ast) { "?" } + PartialQuery.new sql + end + + class Params + def [](name); Substitute.new name; end + end + + class BindMap + def initialize(bind_values) + @value_map = {} + @bind_values = bind_values + + bind_values.each_with_index do |(_, value), i| + if Substitute === value + @value_map[value.name] = i + end + end + end + + def bind(values) + bvs = @bind_values.map { |pair| pair.dup } + values.each { |k,v| bvs[@value_map[k]][1] = v } + bvs + end + end + + def initialize(block = Proc.new) + @mutex = Mutex.new + @relation = nil + @sql = nil + @binds = nil + @block = block + @query_builder = nil + @params = Params.new + end + + def execute(params) + rel = relation @params + + arel = rel.arel + klass = rel.klass + bind_map = binds rel + bind_values = bind_map.bind params + + builder = query_builder klass.connection, arel + sql = builder.sql_for bind_values, klass.connection + + klass.find_by_sql sql, bind_values + end + alias :call :execute + + private + def binds(rel) + @binds || @mutex.synchronize { @binds ||= BindMap.new rel.bind_values } + end + + def query_builder(connection, arel) + @query_builder || @mutex.synchronize { + @query_builder ||= connection.cacheable_query(arel) + } + end + + def sql(klass, arel, bv) + @sql || @mutex.synchronize { + @sql ||= klass.connection.to_sql arel, bv + } end - def execute - @relation.dup.to_a + def relation(values) + @relation || @mutex.synchronize { @relation ||= @block.call(values) } end end end |