seamless_database_pool 1.0.1 → 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile
CHANGED
@@ -22,7 +22,7 @@ end
|
|
22
22
|
|
23
23
|
spec = Gem::Specification.new do |s|
|
24
24
|
s.name = "seamless_database_pool"
|
25
|
-
s.version = "1.0.
|
25
|
+
s.version = "1.0.2"
|
26
26
|
s.author = "Brian Durand"
|
27
27
|
s.platform = Gem::Platform::RUBY
|
28
28
|
s.summary = "Support for master/slave database clusters in ActiveRecord"
|
@@ -31,7 +31,9 @@ module ActiveRecord
|
|
31
31
|
end
|
32
32
|
end if config[:read_pool]
|
33
33
|
|
34
|
-
|
34
|
+
|
35
|
+
klass = ActiveRecord::ConnectionAdapters::SeamlessDatabasePoolAdapter.adapter_class(master_connection)
|
36
|
+
klass.new(nil, logger, master_connection, read_connections, pool_weights)
|
35
37
|
end
|
36
38
|
|
37
39
|
def self.establish_adapter (adapter)
|
@@ -79,12 +81,60 @@ module ActiveRecord
|
|
79
81
|
|
80
82
|
module ConnectionAdapters
|
81
83
|
class SeamlessDatabasePoolAdapter < AbstractAdapter
|
82
|
-
|
83
|
-
READ_ONLY_METHODS = [:select_one, :select_all, :select_value, :select_values, :select_rows, :select]
|
84
|
-
CONNECTION_METHODS = [:adapter_name, :active?, :reconnect!, :disconnect!, :verify!, :reset_runtime]
|
85
84
|
|
86
85
|
attr_reader :read_connections, :master_connection
|
87
86
|
|
87
|
+
# Create an anonymous class that extends this one and proxies methods to the pool connections.
|
88
|
+
def self.adapter_class (master_connection)
|
89
|
+
# Define methods to proxy to the appropriate pool
|
90
|
+
read_only_methods = [:select_one, :select_all, :select_value, :select_values, :select_rows, :select]
|
91
|
+
connection_methods = [:adapter_name, :active?, :reconnect!, :disconnect!, :reset!, :verify!, :reset_runtime]
|
92
|
+
master_methods = master_connection.public_methods(false) + master_connection.protected_methods(false) + master_connection.private_methods(false)
|
93
|
+
master_methods -= public_methods(false) + protected_methods(false) + private_methods(false)
|
94
|
+
master_methods = master_methods.collect{|m| m.to_sym}
|
95
|
+
master_methods -= read_only_methods
|
96
|
+
master_methods -= connection_methods
|
97
|
+
master_methods.delete(:transaction)
|
98
|
+
|
99
|
+
klass = Class.new(self)
|
100
|
+
master_methods.each do |method_name|
|
101
|
+
klass.class_eval(%Q(
|
102
|
+
def #{method_name}(*args, &block)
|
103
|
+
begin
|
104
|
+
proxy_connection_method(master_connection, :#{method_name}, :master, *args, &block)
|
105
|
+
rescue DatabaseConnectionError => e
|
106
|
+
raise e.wrapped_exception
|
107
|
+
end
|
108
|
+
end
|
109
|
+
))
|
110
|
+
end
|
111
|
+
|
112
|
+
read_only_methods.each do |method_name|
|
113
|
+
klass.class_eval(%Q(
|
114
|
+
def #{method_name}(*args, &block)
|
115
|
+
connection = current_read_connection
|
116
|
+
begin
|
117
|
+
proxy_connection_method(connection, :#{method_name}, :read, *args, &block)
|
118
|
+
rescue DatabaseConnectionError => e
|
119
|
+
unless block or using_master_connection?
|
120
|
+
# Try again with a different connection if needed unless it could have a side effect
|
121
|
+
unless connection.active?
|
122
|
+
suppress_read_connection(connection, 30)
|
123
|
+
connection = current_read_connection
|
124
|
+
SeamlessDatabasePool.set_persistent_read_connection(self, connection)
|
125
|
+
end
|
126
|
+
proxy_connection_method(connection, :#{method_name}, :retry, *args, &block)
|
127
|
+
else
|
128
|
+
raise e.wrapped_exception
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
))
|
133
|
+
end
|
134
|
+
|
135
|
+
return klass
|
136
|
+
end
|
137
|
+
|
88
138
|
def initialize (connection, logger, master_connection, read_connections, pool_weights)
|
89
139
|
super(connection, logger)
|
90
140
|
|
@@ -96,8 +146,6 @@ module ActiveRecord
|
|
96
146
|
weight.times{@weighted_read_connections << conn}
|
97
147
|
end
|
98
148
|
@available_read_connections = [AvailableConnections.new(@weighted_read_connections)]
|
99
|
-
|
100
|
-
define_proxy_methods
|
101
149
|
end
|
102
150
|
|
103
151
|
def adapter_name #:nodoc:
|
@@ -128,8 +176,12 @@ module ActiveRecord
|
|
128
176
|
all_connections.each{|conn| conn.disconnect!}
|
129
177
|
end
|
130
178
|
|
131
|
-
def
|
132
|
-
all_connections.each{|conn| conn.
|
179
|
+
def reset!
|
180
|
+
all_connections.each{|conn| conn.reset!}
|
181
|
+
end
|
182
|
+
|
183
|
+
def verify!(*ignored)
|
184
|
+
all_connections.each{|conn| conn.verify!(*ignored)}
|
133
185
|
end
|
134
186
|
|
135
187
|
def reset_runtime
|
@@ -254,49 +306,6 @@ module ActiveRecord
|
|
254
306
|
end
|
255
307
|
end
|
256
308
|
|
257
|
-
# Define proxy methods to handle actual database work.
|
258
|
-
def define_proxy_methods
|
259
|
-
master_methods = master_connection.public_methods(false) + master_connection.protected_methods(false) + master_connection.private_methods(false)
|
260
|
-
master_methods -= READ_ONLY_METHODS
|
261
|
-
master_methods -= CONNECTION_METHODS
|
262
|
-
master_methods.delete(:transaction)
|
263
|
-
|
264
|
-
master_methods.each do |method_name|
|
265
|
-
instance_eval(%Q(
|
266
|
-
def #{method_name}(*args, &block)
|
267
|
-
begin
|
268
|
-
proxy_connection_method(master_connection, :#{method_name}, :master, *args, &block)
|
269
|
-
rescue DatabaseConnectionError => e
|
270
|
-
raise e.wrapped_exception
|
271
|
-
end
|
272
|
-
end
|
273
|
-
))
|
274
|
-
end
|
275
|
-
|
276
|
-
READ_ONLY_METHODS.each do |method_name|
|
277
|
-
instance_eval(%Q(
|
278
|
-
def #{method_name}(*args, &block)
|
279
|
-
connection = current_read_connection
|
280
|
-
begin
|
281
|
-
proxy_connection_method(connection, :#{method_name}, :read, *args, &block)
|
282
|
-
rescue DatabaseConnectionError => e
|
283
|
-
unless block or using_master_connection?
|
284
|
-
# Try again with a different connection if needed unless it could have a side effect
|
285
|
-
unless connection.active?
|
286
|
-
suppress_read_connection(connection, 30)
|
287
|
-
connection = current_read_connection
|
288
|
-
SeamlessDatabasePool.set_persistent_read_connection(self, connection)
|
289
|
-
end
|
290
|
-
proxy_connection_method(connection, :#{method_name}, :retry, *args, &block)
|
291
|
-
else
|
292
|
-
raise e.wrapped_exception
|
293
|
-
end
|
294
|
-
end
|
295
|
-
end
|
296
|
-
))
|
297
|
-
end
|
298
|
-
end
|
299
|
-
|
300
309
|
end
|
301
310
|
end
|
302
311
|
end
|
@@ -8,7 +8,7 @@ module SeamlessDatabasePool
|
|
8
8
|
# Example:
|
9
9
|
#
|
10
10
|
# ApplicationController < ActionController::Base
|
11
|
-
# include SeamlessDatabasePool
|
11
|
+
# include SeamlessDatabasePool::ControllerFilter
|
12
12
|
# use_database_pool :all => :persistent, [:save, :delete] => :master
|
13
13
|
# ...
|
14
14
|
|
data/spec/filter_spec.rb
CHANGED
@@ -9,7 +9,7 @@ describe "SeamlessDatabasePool::ControllerFilter" do
|
|
9
9
|
|
10
10
|
def initialize(action, session = nil)
|
11
11
|
@action_name = action
|
12
|
-
session ||=
|
12
|
+
session ||= {}
|
13
13
|
@session = session
|
14
14
|
end
|
15
15
|
|
@@ -89,7 +89,7 @@ describe "SeamlessDatabasePool::ControllerFilter" do
|
|
89
89
|
end
|
90
90
|
|
91
91
|
it "should be able to force using the master connection on the next request" do
|
92
|
-
session =
|
92
|
+
session = {}
|
93
93
|
|
94
94
|
# First request
|
95
95
|
controller = SeamlessDatabasePool::TestOtherController.new('read', session)
|
@@ -116,7 +116,7 @@ describe "SeamlessDatabasePool::ControllerFilter" do
|
|
116
116
|
end
|
117
117
|
|
118
118
|
it "should force the master connection on the next request for a redirect in master connection block" do
|
119
|
-
session =
|
119
|
+
session = {}
|
120
120
|
controller = SeamlessDatabasePool::TestOtherController.new('redirect_master_action', session)
|
121
121
|
controller.perform_action.should == {:action => :read}
|
122
122
|
|
@@ -125,7 +125,7 @@ describe "SeamlessDatabasePool::ControllerFilter" do
|
|
125
125
|
end
|
126
126
|
|
127
127
|
it "should not force the master connection on the next request for a redirect not in master connection block" do
|
128
|
-
session =
|
128
|
+
session = {}
|
129
129
|
controller = SeamlessDatabasePool::TestOtherController.new('redirect_read_action', session)
|
130
130
|
controller.perform_action.should == {:action => :read}
|
131
131
|
|
@@ -52,7 +52,9 @@ describe "SeamlessDatabasePoolAdapter ActiveRecord::Base extension" do
|
|
52
52
|
ActiveRecord::Base.should_receive(:reader_connection).with(:adapter => 'reader', :host => 'read_host_1', :username => 'user', :pool_weight => 1).and_return(read_connection_1)
|
53
53
|
ActiveRecord::Base.should_receive(:reader_connection).with(:adapter => 'reader', :host => 'read_host_2', :username => 'user', :pool_weight => 2).and_return(read_connection_2)
|
54
54
|
|
55
|
-
|
55
|
+
klass = mock(:class)
|
56
|
+
ActiveRecord::ConnectionAdapters::SeamlessDatabasePoolAdapter.should_receive(:adapter_class).with(master_connection).and_return(klass)
|
57
|
+
klass.should_receive(:new).with(nil, logger, master_connection, [read_connection_1, read_connection_2], weights).and_return(pool_connection)
|
56
58
|
|
57
59
|
ActiveRecord::Base.should_receive(:establish_adapter).with('writer')
|
58
60
|
ActiveRecord::Base.should_receive(:establish_adapter).with('reader').twice
|
@@ -72,7 +74,8 @@ describe "SeamlessDatabasePoolAdapter" do
|
|
72
74
|
@read_connection_1 = SeamlessDatabasePool::MockConnection.new("read_1")
|
73
75
|
@read_connection_2 = SeamlessDatabasePool::MockConnection.new("read_2")
|
74
76
|
weights = {@master_connection => 1, @read_connection_1 => 1, @read_connection_2 => 2}
|
75
|
-
|
77
|
+
connection_class = ActiveRecord::ConnectionAdapters::SeamlessDatabasePoolAdapter.adapter_class(@master_connection)
|
78
|
+
@pool_connection = connection_class.new(nil, mock(:logger), @master_connection, [@read_connection_1, @read_connection_2], weights)
|
76
79
|
end
|
77
80
|
|
78
81
|
it "should initialize the connection pool" do
|
@@ -102,7 +105,8 @@ describe "SeamlessDatabasePoolAdapter" do
|
|
102
105
|
end
|
103
106
|
|
104
107
|
it "should use the master connection in a block" do
|
105
|
-
|
108
|
+
connection_class = ActiveRecord::ConnectionAdapters::SeamlessDatabasePoolAdapter.adapter_class(@master_connection)
|
109
|
+
connection = connection_class.new(nil, mock(:logger), @master_connection, [@read_connection_1], {@read_connection_1 => 1})
|
106
110
|
connection.random_read_connection.should == @read_connection_1
|
107
111
|
connection.use_master_connection do
|
108
112
|
connection.random_read_connection.should == @master_connection
|
@@ -111,7 +115,8 @@ describe "SeamlessDatabasePoolAdapter" do
|
|
111
115
|
end
|
112
116
|
|
113
117
|
it "should use the master connection inside a transaction" do
|
114
|
-
|
118
|
+
connection_class = ActiveRecord::ConnectionAdapters::SeamlessDatabasePoolAdapter.adapter_class(@master_connection)
|
119
|
+
connection = connection_class.new(nil, mock(:logger), @master_connection, [@read_connection_1], {@read_connection_1 => 1})
|
115
120
|
@master_connection.should_receive(:transaction).with(true).and_yield
|
116
121
|
@master_connection.should_receive(:select_one).with('Transaction SQL')
|
117
122
|
@read_connection_1.should_receive(:select_one).with('SQL 1')
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: seamless_database_pool
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brian Durand
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date:
|
12
|
+
date: 2009-05-06 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -61,7 +61,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
61
61
|
requirements:
|
62
62
|
- rspec 1.0.8 or higher is needed to run the tests
|
63
63
|
rubyforge_project: seamlessdbpool
|
64
|
-
rubygems_version: 1.
|
64
|
+
rubygems_version: 1.3.1
|
65
65
|
signing_key:
|
66
66
|
specification_version: 2
|
67
67
|
summary: Support for master/slave database clusters in ActiveRecord
|