From f3e379f0c97149bb29a63dc9db8a2836addcd957 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 15 Jan 2014 14:24:11 -0800 Subject: use a params hash so we know what bind parameters are used --- .../lib/active_record/relation/query_methods.rb | 2 +- activerecord/lib/active_record/statement_cache.rb | 57 ++++++++++++++++++---- activerecord/test/cases/statement_cache_test.rb | 31 ++++++------ 3 files changed, 65 insertions(+), 25 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index ffcdcd1169..226f1b8176 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -948,7 +948,7 @@ module ActiveRecord def create_binds(opts, idx) bindable, non_binds = opts.partition do |column, value| case value - when String, Integer + when String, Integer, ActiveRecord::StatementCache::Substitute @klass.columns_hash.include? column.to_s else false diff --git a/activerecord/lib/active_record/statement_cache.rb b/activerecord/lib/active_record/statement_cache.rb index 90d4748d84..8372d54c15 100644 --- a/activerecord/lib/active_record/statement_cache.rb +++ b/activerecord/lib/active_record/statement_cache.rb @@ -14,25 +14,64 @@ 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 + Substitute = Struct.new :name + + 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 |(column, 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 + @params = Params.new end - def execute(*vals) - rel = relation vals - @mutex.synchronize do - rel.set_binds vals - rel.to_a - end + def execute(params) + rel = relation @params + + arel = rel.arel + klass = rel.klass + bv = binds rel + + klass.find_by_sql sql(klass, arel, bv), bv.bind(params) end + alias :call :execute private - def relation(values) - @relation || @mutex.synchronize { - @block.call(*values) + def binds(rel) + @binds || @mutex.synchronize { @binds ||= BindMap.new rel.bind_values } + end + + def sql(klass, arel, bv) + @sql || @mutex.synchronize { + @sql ||= klass.connection.to_sql arel, bv } end + + def relation(values) + @relation || @mutex.synchronize { @relation ||= @block.call(values) } + end end end diff --git a/activerecord/test/cases/statement_cache_test.rb b/activerecord/test/cases/statement_cache_test.rb index 88ac2cf2eb..efd2d94b57 100644 --- a/activerecord/test/cases/statement_cache_test.rb +++ b/activerecord/test/cases/statement_cache_test.rb @@ -7,6 +7,7 @@ require 'models/electron' module ActiveRecord class StatementCacheTest < ActiveRecord::TestCase def setup + skip if current_adapter?(:Mysql2Adapter) @connection = ActiveRecord::Base.connection end @@ -15,13 +16,13 @@ module ActiveRecord Book.create(name: "my book") Book.create(name: "my other book") - cache = StatementCache.new do |name| - Book.where(:name => name) + cache = StatementCache.new do |params| + Book.where(:name => params[:name]) end - b = cache.execute "my book" + b = cache.execute name: "my book" assert_equal "my book", b[0].name - b = cache.execute "my other book" + b = cache.execute name: "my other book" assert_equal "my other book", b[0].name end @@ -30,13 +31,13 @@ module ActiveRecord b1 = Book.create(name: "my book") b2 = Book.create(name: "my other book") - cache = StatementCache.new do |id| - Book.where(id: id) + cache = StatementCache.new do |params| + Book.where(id: params[:id]) end - b = cache.execute b1.id + b = cache.execute id: b1.id assert_equal b1.name, b[0].name - b = cache.execute b2.id + b = cache.execute id: b2.id assert_equal b2.name, b[0].name end @@ -53,18 +54,18 @@ module ActiveRecord #End def test_statement_cache_with_simple_statement - cache = ActiveRecord::StatementCache.new do + cache = ActiveRecord::StatementCache.new do |params| Book.where(name: "my book").where("author_id > 3") end Book.create(name: "my book", author_id: 4) - books = cache.execute + books = cache.execute({}) assert_equal "my book", books[0].name end def test_statement_cache_with_complex_statement - cache = ActiveRecord::StatementCache.new do + cache = ActiveRecord::StatementCache.new do |params| Liquid.joins(:molecules => :electrons).where('molecules.name' => 'dioxane', 'electrons.name' => 'lepton') end @@ -72,12 +73,12 @@ module ActiveRecord molecule = salty.molecules.create(name: 'dioxane') molecule.electrons.create(name: 'lepton') - liquids = cache.execute + liquids = cache.execute({}) assert_equal "salty", liquids[0].name end def test_statement_cache_values_differ - cache = ActiveRecord::StatementCache.new do + cache = ActiveRecord::StatementCache.new do |params| Book.where(name: "my book") end @@ -85,13 +86,13 @@ module ActiveRecord Book.create(name: "my book") end - first_books = cache.execute + first_books = cache.execute({}) 3.times do Book.create(name: "my book") end - additional_books = cache.execute + additional_books = cache.execute({}) assert first_books != additional_books end end -- cgit v1.2.3