Skip to content

Commit

Permalink
Fallback to single queries for adapters not supporting futures. Closes
Browse files Browse the repository at this point in the history
  • Loading branch information
leoasis committed Apr 28, 2013
1 parent db3cc4f commit 9a3f871
Show file tree
Hide file tree
Showing 10 changed files with 164 additions and 57 deletions.
12 changes: 9 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@ language: ruby
rvm:
- 1.9.3
env:
- ADAPTER=mysql activerecord=3.2.11
- ADAPTER=mysql activerecord=3.2.12
- ADAPTER=mysql activerecord=3.2.13
- ADAPTER=future_enabled_mysql2 activerecord=3.2.11
- ADAPTER=future_enabled_mysql2 activerecord=3.2.12
- ADAPTER=future_enabled_mysql2 activerecord=3.2.13
- ADAPTER=mysql2 activerecord=3.2.11
- ADAPTER=mysql2 activerecord=3.2.12
- ADAPTER=mysql2 activerecord=3.2.13
- ADAPTER=future_enabled_postgresql activerecord=3.2.11
- ADAPTER=future_enabled_postgresql activerecord=3.2.12
- ADAPTER=future_enabled_postgresql activerecord=3.2.13
- ADAPTER=postgresql activerecord=3.2.11
- ADAPTER=postgresql activerecord=3.2.12
- ADAPTER=postgresql activerecord=3.2.13
Expand Down
4 changes: 3 additions & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ RSpec::Core::RakeTask.new(:spec)

task :default => :spec

ADAPTERS = %w(future_enabled_mysql2 future_enabled_postgresql postgresql mysql2)

desc "Runs the specs with all databases"
task :all do
success = true
["mysql", "postgresql"].each do |adapter|
ADAPTERS.each do |adapter|
status = system({ "ADAPTER" => adapter }, "bundle exec rspec")
success &&= status
end
Expand Down
11 changes: 3 additions & 8 deletions lib/active_record/futures.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,13 @@ def self.future_calculation_methods
end

def future
supports_futures = connection.respond_to?(:supports_futures?) &&
connection.supports_futures?

# simply pass through if the connection adapter does not support
# futures
supports_futures ? FutureRelation.new(self) : self
FutureRelation.new(self)
end

def future_pluck(column_name)
exec = lambda { pluck(column_name) }
query = record_query(&exec)
FutureCalculationArray.new(query, exec)
FutureCalculationArray.new(self, query, exec)
end

method_table = Hash[future_calculation_methods.zip(original_calculation_methods)]
Expand All @@ -33,7 +28,7 @@ def future_pluck(column_name)
define_method(future_method) do |*args, &block|
exec = lambda { send(method, *args, &block) }
query = record_query(&exec)
FutureCalculationValue.new(query, exec)
FutureCalculationValue.new(self, query, exec)
end
end
end
Expand Down
15 changes: 13 additions & 2 deletions lib/active_record/futures/future.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@ def fetch_with(method)
end


attr_reader :result
attr_reader :result, :relation
private :relation

def initialize
def initialize(relation)
@relation = relation
Future.register(self)
end

Expand All @@ -54,6 +56,10 @@ def fulfilled?
end

def load
# Only perform a load if the adapter supports futures.
# This allows to fallback to normal query execution in futures
# when the adapter does not support futures.
return unless connection_supports_futures?
Future.current = self
execute
Future.current = nil
Expand All @@ -76,6 +82,11 @@ def executed?
end
undef_method :executed?

def connection_supports_futures?
conn = relation.connection
conn.respond_to?(:supports_futures?) && conn.supports_futures?
end

end
end
end
4 changes: 2 additions & 2 deletions lib/active_record/futures/future_calculation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ class FutureCalculation < Future
attr_reader :query, :execution
private :query, :execution

def initialize(query, execution)
super()
def initialize(relation, query, execution)
super(relation)
@query = query
@execution = execution
end
Expand Down
6 changes: 1 addition & 5 deletions lib/active_record/futures/future_relation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,8 @@ module Futures
class FutureRelation < Future
include ActiveRecord::Delegation

attr_reader :relation
private :relation

def initialize(relation)
super()
@relation = relation
super
@klass = relation.klass

# Eagerly get sql from relation, since PostgreSQL adapter may use the
Expand Down
10 changes: 9 additions & 1 deletion spec/active_record/futures/future_relation_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@

module ActiveRecord::Futures
describe FutureRelation do
let(:relation) { double(ActiveRecord::Relation, klass: Class.new, to_a: nil, to_sql: "select 1") }
let(:relation) do
double(ActiveRecord::Relation, {
klass: Class.new,
to_a: nil,
to_sql: "select 1",
connection: double("connection", supports_futures?: true)
})
end

subject { FutureRelation.new(relation) }

describe ".new" do
Expand Down
127 changes: 99 additions & 28 deletions spec/in_action/combination_of_futures_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,71 +12,142 @@
end

let(:user_relation) { User.where(name: "Lenny") }
let(:other_user_relation) { User.where("name like 'J%'") }

let(:post_relation) { Post.where(title: "Post title 2") }

let(:user_relation_sql) { user_relation.to_sql }
let!(:user_future_relation) { user_relation.future }

let(:other_user_relation) { User.where("name like 'J%'") }
let(:other_user_relation_count_sql) { count(other_user_relation).to_sql }
let!(:user_future_value) { other_user_relation.future_count }

let(:post_relation) { Post.where(title: "Post title 2") }
let(:post_relation_sql) { post_relation.to_sql }
let!(:post_future_relation) { post_relation.future }

let(:post_count_sql) { count(Post.scoped).to_sql }
let!(:post_future_value) { Post.future_count }

context "the execution of any future" do
subject { -> { post_future_relation.to_a } }

let(:futures_sql) do
[
user_relation.to_sql,
count(other_user_relation).to_sql,
post_relation.to_sql,
count(Post.scoped).to_sql
].join(';')
context "execs only once with all queries", :supporting_adapter do
let(:futures_sql) do
[
user_relation_sql,
other_user_relation_count_sql,
post_relation_sql,
post_count_sql
].join(';')
end

it { should exec(1).query }
it { should exec_query(futures_sql) }
end

it { should exec(1).query }
it { should exec_query(futures_sql) }

def count(relation)
arel = relation.arel
arel.projections = []
arel.project("COUNT(*)")
arel
context "execs just the executed future's query", :not_supporting_adapter do
it { should exec(1).query }
it { should exec_query(post_future_relation.to_sql) }
end
end

context "having executed a future" do
context "having executed the post future" do
before do
post_future_relation.to_a
end

context "the user future relation" do
subject { user_future_relation }

it { should be_fulfilled }
it(nil, :supporting_adapter) { should be_fulfilled }
it(nil, :not_supporting_adapter) { should_not be_fulfilled }

describe "#to_a" do
let(:calling_to_a) { ->{ subject.to_a } }

its(:to_a) { should eq user_relation.to_a }
its(:to_a) { should eq user_relation.to_a }

context "when adapter supports futures", :supporting_adapter do
specify { calling_to_a.should exec(0).queries }
end

context "when adapter does not support futures", :not_supporting_adapter do
specify { calling_to_a.should exec(1).query }
specify { calling_to_a.should exec_query(user_relation_sql) }
end
end
end

context "the user future value" do
subject { user_future_value }

it { should be_fulfilled }
its(:value) { should eq other_user_relation.count }
it(nil, :supporting_adapter) { should be_fulfilled }
it(nil, :not_supporting_adapter) { should_not be_fulfilled }

describe "#value" do
let(:calling_value) { ->{ subject.value } }

its(:value) { should eq other_user_relation.count }

context "when adapter supports futures", :supporting_adapter do
specify { calling_value.should exec(0).queries }
end

context "when adapter does not support futures", :not_supporting_adapter do
specify { calling_value.should exec(1).query }
specify { calling_value.should exec_query(other_user_relation_count_sql) }
end
end
end

context "the post future relation" do
subject { post_future_relation }

it { should be_fulfilled }
its(:to_a) { should eq post_relation.to_a }
it(nil, :supporting_adapter) { should be_fulfilled }
it(nil, :not_supporting_adapter) { should_not be_fulfilled }

describe "#to_a" do
let(:calling_to_a) { ->{ subject.to_a } }

its(:to_a) { should eq post_relation.to_a }

context "when adapter supports futures", :supporting_adapter do
specify { calling_to_a.should exec(0).queries }
end

context "when adapter does not support futures", :not_supporting_adapter do
specify { calling_to_a.should exec(0).query }
# No queries should be executed, since this is the future we executed
# before
end
end
end

context "the post future value" do
subject { post_future_value }

it { should be_fulfilled }
its(:value) { should eq Post.count }
it(nil, :supporting_adapter) { should be_fulfilled }
it(nil, :not_supporting_adapter) { should_not be_fulfilled }

describe "#value" do
let(:calling_value) { ->{ subject.value } }

its(:value) { should eq Post.count }

context "when adapter supports futures", :supporting_adapter do
specify { calling_value.should exec(0).queries }
end

context "when adapter does not support futures", :not_supporting_adapter do
specify { calling_value.should exec(1).query }
specify { calling_value.should exec_query(post_count_sql) }
end
end
end
end

def count(relation)
arel = relation.arel
arel.projections = []
arel.project("COUNT(*)")
arel
end
end
9 changes: 6 additions & 3 deletions spec/in_action/future_fulfillment_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ module ActiveRecord::Futures
context "the future" do
subject { future }

it { should be_fulfilled }
it(nil, :supporting_adapter) { should be_fulfilled }
it(nil, :not_supporting_adapter) { should_not be_fulfilled }
end
end
end
Expand All @@ -46,11 +47,13 @@ module ActiveRecord::Futures
its(:all) { should have(0).futures }

context "the first relation future" do
specify { future.should be_fulfilled }
specify(nil, :supporting_adapter) { future.should be_fulfilled }
specify(nil, :not_supporting_adapter) { future.should_not be_fulfilled }
end

context "the other relation future" do
specify { another_future.should be_fulfilled }
specify(nil, :supporting_adapter) { another_future.should be_fulfilled }
specify(nil, :not_supporting_adapter) { another_future.should_not be_fulfilled }
end
end

Expand Down
23 changes: 19 additions & 4 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,40 @@
require 'activerecord-futures'

configs = {
mysql: {
future_enabled_mysql2: {
adapter: "future_enabled_mysql2",
database: "activerecord_futures_test",
username: "root",
encoding: "utf8"
},
postgresql: {
future_enabled_postgresql: {
adapter: "future_enabled_postgresql",
database: "activerecord_futures_test",
username: "postgres"
},
postgresql: {
adapter: "postgresql",
database: "activerecord_futures_test",
username: "postgres"
},
mysql2: {
adapter: "mysql2",
database: "activerecord_futures_test",
username: "root",
encoding: "utf8"
}
}

env_config = ENV['ADAPTER'].try(:to_sym)
config_key = configs.keys.include?(env_config) ? env_config : :mysql
config_key = configs.keys.include?(env_config) ? env_config : :future_enabled_mysql2
config = configs[config_key]
puts "Using #{config_key} configuration"

ActiveRecord::Base.establish_connection(config)
supports_futures =
ActiveRecord::Base.connection.respond_to?(:supports_futures?) &&
ActiveRecord::Base.connection.supports_futures?

require 'db/schema'
Dir['./spec/models/**/*.rb'].each { |f| require f }

Expand All @@ -34,7 +49,7 @@
config.treat_symbols_as_metadata_keys_with_true_values = true
config.run_all_when_everything_filtered = true
config.filter_run :focus

config.filter_run_excluding(supports_futures ? :not_supporting_adapter : :supporting_adapter)
# Run specs in random order to surface order dependencies. If you find an
# order dependency and want to debug it, you can fix the order by providing
# the seed, which is printed after each run.
Expand Down

0 comments on commit 9a3f871

Please sign in to comment.