Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix to propagate shard conditions to
AssociationRelation
too
backport from develop-3-0 branch When calling `@user.tasks.find_by(name: 'xxx')` before: SELECT `tasks`.* FROM `tasks` WHERE `tasks`.`name` = "xxx" # raises CannotSpecifyShardError after: SELECT `tasks`.* FROM `tasks` WHERE `tasks`.`user_id` = 1 AND `tasks`.`name` = "xxx"
- Loading branch information
gussan
committed
Jan 4, 2017
1 parent
76dfbd6
commit 3777af2
Showing
5 changed files
with
53 additions
and
127 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
139 changes: 16 additions & 123 deletions
139
lib/active_record/turntable/active_record_ext/association.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,139 +1,32 @@ | ||
require 'active_record/associations' | ||
require "active_record/associations" | ||
|
||
module ActiveRecord::Turntable | ||
module ActiveRecordExt | ||
module Association | ||
extend ActiveSupport::Concern | ||
include ShardingCondition | ||
|
||
included do | ||
ActiveRecord::Associations::SingularAssociation.send(:include, SingularAssociationExt) | ||
ActiveRecord::Associations::CollectionAssociation.send(:include, CollectionAssociationExt) | ||
ActiveRecord::Associations::Builder::Association.valid_options += [:foreign_shard_key] | ||
def self.prepended(mod) | ||
ActiveRecord::Associations::Builder::Association.valid_options << :foreign_shard_key | ||
end | ||
|
||
private | ||
|
||
def turntable_scope(scope, bind = nil) | ||
if should_use_shard_key? | ||
scope = scope.where(klass.turntable_shard_key => owner.send(foreign_shard_key)) | ||
end | ||
scope | ||
end | ||
|
||
module SingularAssociationExt | ||
extend ActiveSupport::Concern | ||
|
||
included do | ||
if Util.ar42_or_later? | ||
alias_method_chain :get_records, :turntable | ||
else | ||
alias_method_chain :find_target, :turntable | ||
end | ||
end | ||
|
||
# @note Override to add sharding condition for singular association | ||
if Util.ar42_or_later? | ||
def get_records_with_turntable | ||
if reflection.scope_chain.any?(&:any?) || | ||
scope.eager_loading? || | ||
klass.current_scope || | ||
klass.default_scopes.any? || | ||
should_use_shard_key? # OPTIMIZE: Use bind values if cachable scope | ||
|
||
return turntable_scope(scope).limit(1).to_a | ||
end | ||
|
||
conn = klass.connection | ||
sc = reflection.association_scope_cache(conn, owner) do | ||
ActiveRecord::StatementCache.create(conn) { |params| | ||
as = ActiveRecord::Associations::AssociationScope.create { params.bind } | ||
target_scope.merge(as.scope(self, conn)).limit(1) | ||
} | ||
end | ||
|
||
binds = ActiveRecord::Associations::AssociationScope.get_bind_values(owner, reflection.chain) | ||
sc.execute binds, klass, klass.connection | ||
end | ||
elsif Util.ar41_or_later? | ||
def find_target_with_turntable | ||
if record = turntable_scope(scope).take | ||
set_inverse_instance record | ||
end | ||
end | ||
else | ||
def find_target_with_turntable | ||
turntable_scope(scope).take.tap { |record| set_inverse_instance(record) } | ||
end | ||
end | ||
end | ||
protected | ||
|
||
module CollectionAssociationExt | ||
extend ActiveSupport::Concern | ||
# @note Override to pass shard key conditions | ||
def target_scope | ||
return super unless should_use_shard_key? | ||
|
||
included do | ||
if Util.ar42_or_later? | ||
alias_method_chain :get_records, :turntable | ||
else | ||
alias_method_chain :find_target, :turntable | ||
end | ||
scope = klass.where( | ||
klass.turntable_shard_key => | ||
owner.send(foreign_shard_key) | ||
) | ||
super.merge!(scope) | ||
end | ||
|
||
private | ||
|
||
if Util.ar42_or_later? | ||
def get_records_with_turntable | ||
if reflection.scope_chain.any?(&:any?) || | ||
scope.eager_loading? || | ||
klass.current_scope || | ||
klass.default_scopes.any? || | ||
should_use_shard_key? # OPTIMIZE: Use bind values if cachable scope | ||
|
||
return turntable_scope(scope).to_a | ||
end | ||
|
||
conn = klass.connection | ||
sc = reflection.association_scope_cache(conn, owner) do | ||
ActiveRecord::StatementCache.create(conn) { |params| | ||
as = ActiveRecord::Associations::AssociationScope.create { params.bind } | ||
target_scope.merge as.scope(self, conn) | ||
} | ||
end | ||
|
||
binds = ActiveRecord::Associations::AssociationScope.get_bind_values(owner, reflection.chain) | ||
sc.execute binds, klass, klass.connection | ||
end | ||
else | ||
# @note Override to add sharding condition for collection association | ||
def find_target_with_turntable | ||
records = | ||
if options[:finder_sql] | ||
reflection.klass.find_by_sql(custom_finder_sql) | ||
else | ||
current_scope = scope | ||
if should_use_shard_key? | ||
current_scope = current_scope.where(klass.turntable_shard_key => owner.send(foreign_shard_key)) | ||
end | ||
current_scope.to_a | ||
end | ||
records.each { |record| set_inverse_instance(record) } | ||
records | ||
end | ||
end | ||
end | ||
|
||
private | ||
|
||
def foreign_shard_key | ||
options[:foreign_shard_key] || owner.turntable_shard_key | ||
end | ||
|
||
def should_use_shard_key? | ||
same_association_shard_key? || !!options[:foreign_shard_key] | ||
end | ||
|
||
def same_association_shard_key? | ||
owner.class.turntable_enabled? && klass.turntable_enabled? && foreign_shard_key == klass.turntable_shard_key | ||
end | ||
def skip_statement_cache? | ||
super || should_use_shard_key? | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
module ActiveRecord::Turntable | ||
module ShardingCondition | ||
private | ||
|
||
def foreign_shard_key | ||
options[:foreign_shard_key] || foreign_target_model.turntable_shard_key | ||
end | ||
|
||
def foreign_target_model | ||
respond_to?(:model) ? model : owner | ||
end | ||
|
||
def should_use_shard_key? | ||
sharded_by_same_key? || !!options[:foreign_shard_key] | ||
end | ||
|
||
def sharded_by_same_key? | ||
foreign_target_model.turntable_enabled? && | ||
klass.turntable_enabled? && | ||
foreign_shard_key == klass.turntable_shard_key | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters