Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add many_to_one_pk_lookup plugin, for using a simple primary key look…
…up for many_to_one associations (great with caching) This can be a major speed boost to models that have many_to_one associations to associated models that use caching, because it will generally use a cached lookup first.
- Loading branch information
1 parent
7acbe2a
commit 04f62c1
Showing
5 changed files
with
218 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
module Sequel | ||
module Plugins | ||
# This is a fairly simple plugin that modifies the internal association loading logic | ||
# for many_to_one associations to use a simple primary key lookup on the associated | ||
# class, which is generally faster as it uses mostly static SQL. Additional, if the | ||
# associated class is caching primary key lookups, you get the benefit of a cached | ||
# lookup. | ||
# | ||
# This plugin is generally not as fast as the prepared_statements_associations plugin | ||
# in the case where the model is not caching primary key lookups, however, it is | ||
# probably significantly faster if the model is caching primary key lookups. If | ||
# the prepared_statements_associations plugin has been loaded first, this | ||
# plugin will only use the primary key lookup code if the associated model is | ||
# caching primary key lookups. | ||
# | ||
# This plugin attempts to determine cases where the primary key lookup would have | ||
# different results than the regular lookup, and use the regular lookup in that case, | ||
# but it cannot handle all situations correctly, which is why it is not Sequel's | ||
# default behavior. | ||
# | ||
# You can disable primary key lookups on a per association basis with this | ||
# plugin using the :many_to_one_pk_lookup=>false association option. | ||
# | ||
# Usage: | ||
# | ||
# # Make all model subclass instances use primary key lookups for many_to_one | ||
# # association loading | ||
# Sequel::Model.plugin :many_to_one_pk_lookup | ||
# | ||
# # Do so for just the album class. | ||
# Album.plugin :many_to_one_pk_lookup | ||
module ManyToOnePkLookup | ||
module InstanceMethods | ||
private | ||
|
||
# If the current association is a fairly simple many_to_one association, use | ||
# a simple primary key lookup on the associated model, which can benefit from | ||
# caching if the associated model is using caching. | ||
def _load_associated_object(opts, dynamic_opts) | ||
klass = opts.associated_class | ||
cache_lookup = opts.fetch(:many_to_one_pk_lookup) do | ||
opts[:many_to_one_pk_lookup] = opts[:type] == :many_to_one && | ||
opts[:key] && | ||
opts.primary_key == klass.primary_key | ||
end | ||
if cache_lookup && | ||
!dynamic_opts[:callback] && | ||
(o = klass.send(:primary_key_lookup, ((fk = opts[:key]).is_a?(Array) ? fk.map{|c| send(c)} : send(fk)))) | ||
o | ||
else | ||
super | ||
end | ||
end | ||
|
||
# Deal with the situation where the prepared_statements_associations plugin is | ||
# loaded first, by using a primary key lookup for many_to_one associations if | ||
# the associated class is using caching, and using the default code otherwise. | ||
# This is done because the prepared_statements_associations code is probably faster | ||
# than the primary key lookup this plugin uses if the model is not caching lookups, | ||
# but probably slower if the model is caching lookups. | ||
def _load_associated_objects(opts, dynamic_opts={}) | ||
if opts.can_have_associated_objects?(self) && opts[:type] == :many_to_one && opts.associated_class.respond_to?(:cache_get_pk) | ||
_load_associated_object(opts, dynamic_opts) | ||
else | ||
super | ||
end | ||
end | ||
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,102 @@ | ||
require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper") | ||
|
||
describe "Sequel::Plugins::ManyToOnePkLookup" do | ||
before do | ||
@cache_class = Class.new(Hash) do | ||
attr_accessor :ttl | ||
def set(k, v, ttl); self[k] = v; @ttl = ttl; end | ||
def get(k); self[k]; end | ||
end | ||
cache = @cache_class.new | ||
@cache = cache | ||
|
||
class ::CachingModel < Sequel::Model | ||
columns :id, :id2 | ||
end | ||
@cc = CachingModel | ||
@cc.plugin :caching, @cache | ||
@cc.dataset._fetch = {:id=>1} | ||
@cm1 = @cc[1] | ||
@cm2 = @cc[2] | ||
@cm12 = @cc[1, 2] | ||
@cm21 = @cc[2, 1] | ||
|
||
class ::LookupModel < ::Sequel::Model | ||
plugin :many_to_one_pk_lookup | ||
columns :id, :caching_model_id, :caching_model_id2 | ||
many_to_one :caching_model | ||
many_to_one :caching_model2, :key=>[:caching_model_id, :caching_model_id2], :class=>:CachingModel | ||
end | ||
@c = LookupModel | ||
|
||
@db = MODEL_DB | ||
@db.reset | ||
end | ||
after do | ||
Object.send(:remove_const, :CachingModel) | ||
Object.send(:remove_const, :LookupModel) | ||
end | ||
|
||
it "should use a simple primary key lookup when retrieving many_to_one associated records via a composite key" do | ||
@cc.set_primary_key([:id, :id2]) | ||
@db.sqls.should == [] | ||
@c.load(:id=>3, :caching_model_id=>1).caching_model.should equal(@cm1) | ||
@c.load(:id=>4, :caching_model_id=>2).caching_model.should equal(@cm2) | ||
@db.sqls.should == [] | ||
@c.load(:id=>4, :caching_model_id=>3).caching_model | ||
@db.sqls.should_not == [] | ||
end | ||
|
||
it "should use a simple primary key lookup when retrieving many_to_one associated records" do | ||
@db.sqls.should == [] | ||
@c.load(:id=>3, :caching_model_id=>1, :caching_model_id2=>2).caching_model2.should equal(@cm12) | ||
@c.load(:id=>3, :caching_model_id=>2, :caching_model_id2=>1).caching_model2.should equal(@cm21) | ||
@db.sqls.should == [] | ||
@c.load(:id=>4, :caching_model_id=>2, :caching_model_id2=>2).caching_model2 | ||
@db.sqls.should_not == [] | ||
end | ||
|
||
it "should not use a simple primary key lookup if the assocation has a nil :key option" do | ||
@c.many_to_one :caching_model, :key=>nil, :dataset=>proc{CachingModel.filter(:caching_model_id=>caching_model_id)} | ||
@c.load(:id=>3, :caching_model_id=>1).caching_model | ||
@db.sqls.should_not == [] | ||
end | ||
|
||
it "should not use a simple primary key lookup if the assocation has a nil :key option" do | ||
@c.many_to_one :caching_model, :many_to_one_pk_lookup=>false | ||
@c.load(:id=>3, :caching_model_id=>1).caching_model | ||
@db.sqls.should_not == [] | ||
end | ||
|
||
it "should not use a simple primary key lookup if the assocation's :primary_key option doesn't match the primary key of the associated class" do | ||
@c.many_to_one :caching_model, :primary_key=>:id2 | ||
@c.load(:id=>3, :caching_model_id=>1).caching_model | ||
@db.sqls.should_not == [] | ||
end | ||
|
||
it "should not use a simple primary key lookup if the prepared_statements_associations method is being used" do | ||
c2 = Class.new(Sequel::Model(:not_caching_model)) | ||
c2.dataset._fetch = {:id=>1} | ||
c = Class.new(Sequel::Model(:lookup_model)) | ||
c.class_eval do | ||
plugin :prepared_statements_associations | ||
plugin :many_to_one_pk_lookup | ||
columns :id, :caching_model_id | ||
many_to_one :caching_model, :class=>c2 | ||
end | ||
c.load(:id=>3, :caching_model_id=>1).caching_model.should == c2.load(:id=>1) | ||
@db.sqls.should_not == [] | ||
end | ||
|
||
it "should use a simple primary key lookup if the prepared_statements_associations method is being used but associated model also uses caching" do | ||
c = Class.new(Sequel::Model(:lookup_model)) | ||
c.class_eval do | ||
plugin :prepared_statements_associations | ||
plugin :many_to_one_pk_lookup | ||
columns :id, :caching_model_id | ||
many_to_one :caching_model | ||
end | ||
c.load(:id=>3, :caching_model_id=>1).caching_model.should equal(@cm1) | ||
@db.sqls.should == [] | ||
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
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