提交 bd3b28f7 编写于 作者: A Aaron Patterson

cache scope building on associations

SQL statements for querying associations are now cached
上级 316ee25c
module ActiveRecord
module Associations
class AssociationScope #:nodoc:
INSTANCE = new
def self.scope(association, connection)
INSTANCE.scope association, connection
end
class BindSubstitution
def initialize(block)
@block = block
end
def bind_value(scope, column, value, alias_tracker)
substitute = alias_tracker.connection.substitute_at(
column, scope.bind_values.length)
scope.bind_values += [[column, @block.call(value)]]
substitute
end
end
def self.create(&block)
block = block ? block : lambda { |val| val }
new BindSubstitution.new(block)
end
def initialize(bind_substitution)
@bind_substitution = bind_substitution
end
INSTANCE = create
def scope(association, connection)
klass = association.klass
reflection = association.reflection
......@@ -22,6 +44,30 @@ def join_type
Arel::Nodes::InnerJoin
end
def self.get_bind_values(owner, chain)
bvs = []
chain.each_with_index do |reflection, i|
if reflection.source_macro == :belongs_to
foreign_key = reflection.foreign_key
else
foreign_key = reflection.active_record_primary_key
end
if reflection == chain.last
bvs << owner[foreign_key]
if reflection.type
bvs << owner.class.base_class.name
end
else
if reflection.type
bvs << chain[i + 1].klass.base_class.name
end
end
end
bvs
end
private
def construct_tables(chain, klass, refl, alias_tracker)
......@@ -49,10 +95,7 @@ def column_for(table_name, column_name, alias_tracker)
end
def bind_value(scope, column, value, alias_tracker)
substitute = alias_tracker.connection.substitute_at(
column, scope.bind_values.length)
scope.bind_values += [[column, value]]
substitute
@bind_substitution.bind_value scope, column, value, alias_tracker
end
def bind(scope, table_name, column_name, value, tracker)
......
......@@ -412,9 +412,23 @@ def null_scope?
end
private
def get_records
return scope.to_a if reflection.scope_chain.any?(&:any?)
conn = klass.connection
sc = reflection.association_scope_cache(conn) do
StatementCache.create(conn) { |params|
as = AssociationScope.create { params.bind }
target_scope.merge as.scope(self, conn)
}
end
binds = AssociationScope.get_bind_values(owner, reflection.chain)
sc.execute binds, klass, klass.connection
end
def find_target
records = scope.to_a
records = get_records
records.each { |record| set_inverse_instance(record) }
records
end
......
......@@ -19,7 +19,11 @@ def to_sql(arel, binds = [])
# This is used in the StatementCache object. It returns an object that
# can be used to query the database repeatedly.
def cacheable_query(arel) # :nodoc:
ActiveRecord::StatementCache.query visitor, arel.ast
if prepared_statements
ActiveRecord::StatementCache.query visitor, arel.ast
else
ActiveRecord::StatementCache.partial_query visitor, arel.ast, collector
end
end
# Returns an ActiveRecord::Result instance.
......
......@@ -44,10 +44,6 @@ def initialize(connection, logger, connection_options, config)
configure_connection
end
def cacheable_query(arel)
ActiveRecord::StatementCache.partial_query visitor, arel.ast, collector
end
MAX_INDEX_LENGTH_FOR_UTF8MB4 = 191
def initialize_schema_migrations_table
if @config[:encoding] == 'utf8mb4'
......
require 'thread'
module ActiveRecord
# = Active Record Reflection
module Reflection # :nodoc:
......@@ -199,6 +201,13 @@ def initialize(macro, name, scope, options, active_record)
@type = options[:as] && "#{options[:as]}_type"
@foreign_type = options[:foreign_type] || "#{name}_type"
@constructable = calculate_constructable(macro, options)
@association_scope_cache = {}
@scope_lock = Mutex.new
end
def association_scope_cache(conn)
key = conn.prepared_statements
@association_scope_cache[key] ||= @scope_lock.synchronize { yield }
end
# Returns a new, unsaved instance of the associated class. +attributes+ will
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册