aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAaron Patterson <aaron.patterson@gmail.com>2014-04-14 16:26:28 -0700
committerAaron Patterson <aaron.patterson@gmail.com>2014-04-14 16:26:28 -0700
commitbd3b28f7f181dce53e872daa23dda101498b8fb4 (patch)
tree99ab0f92b5b3ff42525cab8d872393508a2b5c45
parent316ee25c25a64a2d82c284293a31804077b05d79 (diff)
downloadrails-bd3b28f7f181dce53e872daa23dda101498b8fb4.tar.gz
rails-bd3b28f7f181dce53e872daa23dda101498b8fb4.tar.bz2
rails-bd3b28f7f181dce53e872daa23dda101498b8fb4.zip
cache scope building on associations
SQL statements for querying associations are now cached
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb55
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb16
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb4
-rw-r--r--activerecord/lib/active_record/reflection.rb9
5 files changed, 78 insertions, 12 deletions
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index bb889a8f3b..f1a3b23d5a 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -1,12 +1,34 @@
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 @@ module ActiveRecord
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 @@ module ActiveRecord
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)
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 803e3ab9ab..b623f1375d 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -412,9 +412,23 @@ module ActiveRecord
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
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 b7b9a4363e..bc47412405 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -19,7 +19,11 @@ module ActiveRecord
# 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.
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index a9d260b98c..233af252d6 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -44,10 +44,6 @@ module ActiveRecord
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'
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 1724ea95b0..3123839a10 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -1,3 +1,5 @@
+require 'thread'
+
module ActiveRecord
# = Active Record Reflection
module Reflection # :nodoc:
@@ -199,6 +201,13 @@ module ActiveRecord
@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