db-charmer 1.6.19 → 1.7.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +14 -2
- data/README.rdoc +89 -4
- data/Rakefile +4 -0
- data/db-charmer.gemspec +17 -15
- data/lib/db_charmer.rb +34 -43
- data/lib/db_charmer/{abstract_adapter_extensions.rb → abstract_adapter/log_formatting.rb} +5 -3
- data/lib/db_charmer/action_controller/force_slave_reads.rb +65 -0
- data/lib/db_charmer/{association_preload.rb → active_record/association_preload.rb} +2 -2
- data/lib/db_charmer/{active_record_extensions.rb → active_record/class_attributes.rb} +19 -40
- data/lib/db_charmer/active_record/connection_switching.rb +77 -0
- data/lib/db_charmer/{db_magic.rb → active_record/db_magic.rb} +18 -8
- data/lib/db_charmer/active_record/finder_overrides.rb +61 -0
- data/lib/db_charmer/active_record/migration/multi_db_migrations.rb +71 -0
- data/lib/db_charmer/active_record/multi_db_proxy.rb +65 -0
- data/lib/db_charmer/active_record/named_scope/scope_proxy.rb +26 -0
- data/lib/db_charmer/active_record/sharding.rb +40 -0
- data/lib/db_charmer/connection_factory.rb +1 -1
- data/lib/db_charmer/core_extensions.rb +10 -0
- data/lib/db_charmer/force_slave_reads.rb +36 -0
- data/lib/db_charmer/sharding.rb +0 -36
- data/lib/db_charmer/sharding/method/db_block_group_map.rb +3 -3
- data/lib/db_charmer/sharding/method/db_block_map.rb +3 -3
- data/lib/db_charmer/sharding/stub_connection.rb +4 -4
- data/lib/db_charmer/version.rb +10 -0
- data/lib/tasks/databases.rake +6 -6
- metadata +28 -21
- data/VERSION +0 -1
- data/lib/db_charmer/connection_switch.rb +0 -40
- data/lib/db_charmer/finder_overrides.rb +0 -56
- data/lib/db_charmer/multi_db_migrations.rb +0 -67
- data/lib/db_charmer/multi_db_proxy.rb +0 -63
- data/lib/db_charmer/scope_proxy.rb +0 -22
@@ -1,32 +1,6 @@
|
|
1
1
|
module DbCharmer
|
2
|
-
module
|
3
|
-
module
|
4
|
-
|
5
|
-
def establish_real_connection_if_exists(name, should_exist = false)
|
6
|
-
name = name.to_s
|
7
|
-
|
8
|
-
# Check environment name
|
9
|
-
config = configurations[DbCharmer.env]
|
10
|
-
unless config
|
11
|
-
error = "Invalid environment name (does not exist in database.yml): #{DbCharmer.env}. Please set correct Rails.env or DbCharmer.env."
|
12
|
-
raise ArgumentError, error
|
13
|
-
end
|
14
|
-
|
15
|
-
# Check connection name
|
16
|
-
config = config[name]
|
17
|
-
unless config
|
18
|
-
if should_exist
|
19
|
-
raise ArgumentError, "Invalid connection name (does not exist in database.yml): #{DbCharmer.env}/#{name}"
|
20
|
-
end
|
21
|
-
return # No need to establish connection - they do not want us to
|
22
|
-
end
|
23
|
-
|
24
|
-
# Pass connection name with config
|
25
|
-
config[:connection_name] = name
|
26
|
-
establish_connection(config)
|
27
|
-
end
|
28
|
-
|
29
|
-
#-----------------------------------------------------------------------------
|
2
|
+
module ActiveRecord
|
3
|
+
module ClassAttributes
|
30
4
|
@@db_charmer_opts = {}
|
31
5
|
def db_charmer_opts=(opts)
|
32
6
|
@@db_charmer_opts[self.name] = opts
|
@@ -71,6 +45,23 @@ module DbCharmer
|
|
71
45
|
db_charmer_slaves[rand(db_charmer_slaves.size)]
|
72
46
|
end
|
73
47
|
|
48
|
+
#-----------------------------------------------------------------------------
|
49
|
+
@@db_charmer_force_slave_reads = {}
|
50
|
+
def db_charmer_force_slave_reads=(force)
|
51
|
+
@@db_charmer_force_slave_reads[self.name] = force
|
52
|
+
end
|
53
|
+
|
54
|
+
def db_charmer_force_slave_reads
|
55
|
+
@@db_charmer_force_slave_reads[self.name]
|
56
|
+
end
|
57
|
+
|
58
|
+
# Slave reads are used in two cases:
|
59
|
+
# - per-model slave reads are enabled (see db_magic method for more details)
|
60
|
+
# - global slave reads enforcing is enabled (in a controller action)
|
61
|
+
def db_charmer_force_slave_reads?
|
62
|
+
db_charmer_force_slave_reads || DbCharmer.force_slave_reads?
|
63
|
+
end
|
64
|
+
|
74
65
|
#-----------------------------------------------------------------------------
|
75
66
|
@@db_charmer_connection_levels = Hash.new(0)
|
76
67
|
def db_charmer_connection_level=(level)
|
@@ -105,18 +96,6 @@ module DbCharmer
|
|
105
96
|
raise "Mappings must be nil or respond to []" if mappings && (! mappings.respond_to?(:[]))
|
106
97
|
@@db_charmer_database_remappings = mappings || { }
|
107
98
|
end
|
108
|
-
|
109
|
-
#-----------------------------------------------------------------------------
|
110
|
-
def hijack_connection!
|
111
|
-
return if self.respond_to?(:connection_with_magic)
|
112
|
-
class << self
|
113
|
-
def connection_with_magic
|
114
|
-
db_charmer_remapped_connection || db_charmer_connection_proxy || connection_without_magic
|
115
|
-
end
|
116
|
-
alias_method_chain :connection, :magic
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
99
|
end
|
121
100
|
end
|
122
101
|
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module DbCharmer
|
2
|
+
module ActiveRecord
|
3
|
+
module ConnectionSwitching
|
4
|
+
def establish_real_connection_if_exists(name, should_exist = false)
|
5
|
+
name = name.to_s
|
6
|
+
|
7
|
+
# Check environment name
|
8
|
+
config = configurations[DbCharmer.env]
|
9
|
+
unless config
|
10
|
+
error = "Invalid environment name (does not exist in database.yml): #{DbCharmer.env}. Please set correct Rails.env or DbCharmer.env."
|
11
|
+
raise ArgumentError, error
|
12
|
+
end
|
13
|
+
|
14
|
+
# Check connection name
|
15
|
+
config = config[name]
|
16
|
+
unless config
|
17
|
+
if should_exist
|
18
|
+
raise ArgumentError, "Invalid connection name (does not exist in database.yml): #{DbCharmer.env}/#{name}"
|
19
|
+
end
|
20
|
+
return # No need to establish connection - they do not want us to
|
21
|
+
end
|
22
|
+
|
23
|
+
# Pass connection name with config
|
24
|
+
config[:connection_name] = name
|
25
|
+
establish_connection(config)
|
26
|
+
end
|
27
|
+
|
28
|
+
#-----------------------------------------------------------------------------------------------------------------
|
29
|
+
def hijack_connection!
|
30
|
+
return if self.respond_to?(:connection_with_magic)
|
31
|
+
class << self
|
32
|
+
def connection_with_magic
|
33
|
+
db_charmer_remapped_connection || db_charmer_connection_proxy || connection_without_magic
|
34
|
+
end
|
35
|
+
alias_method_chain :connection, :magic
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
#-----------------------------------------------------------------------------------------------------------------
|
40
|
+
def coerce_to_connection_proxy(conn, should_exist = true)
|
41
|
+
return nil if conn.nil?
|
42
|
+
|
43
|
+
if conn.kind_of?(Symbol) || conn.kind_of?(String)
|
44
|
+
return DbCharmer::ConnectionFactory.connect(conn, should_exist)
|
45
|
+
end
|
46
|
+
|
47
|
+
if conn.kind_of?(Hash)
|
48
|
+
conn = conn.symbolize_keys
|
49
|
+
raise ArgumentError, "Missing required :connection_name parameter" unless conn[:connection_name]
|
50
|
+
return DbCharmer::ConnectionFactory.connect_to_db(conn[:connection_name], conn)
|
51
|
+
end
|
52
|
+
|
53
|
+
if conn.respond_to?(:db_charmer_connection_proxy)
|
54
|
+
return conn.db_charmer_connection_proxy
|
55
|
+
end
|
56
|
+
|
57
|
+
if conn.kind_of?(::ActiveRecord::ConnectionAdapters::AbstractAdapter) || conn.kind_of?(DbCharmer::Sharding::StubConnection)
|
58
|
+
return conn
|
59
|
+
end
|
60
|
+
|
61
|
+
raise "Unsupported connection type: #{conn.class}"
|
62
|
+
end
|
63
|
+
|
64
|
+
#-----------------------------------------------------------------------------------------------------------------
|
65
|
+
def switch_connection_to(conn, require_config_to_exist = true)
|
66
|
+
new_conn = coerce_to_connection_proxy(conn, require_config_to_exist)
|
67
|
+
|
68
|
+
if db_charmer_connection_proxy.is_a?(DbCharmer::Sharding::StubConnection)
|
69
|
+
db_charmer_connection_proxy.set_real_connection(new_conn)
|
70
|
+
end
|
71
|
+
|
72
|
+
self.db_charmer_connection_proxy = new_conn
|
73
|
+
self.hijack_connection!
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module DbCharmer
|
2
|
-
module
|
3
|
-
module
|
2
|
+
module ActiveRecord
|
3
|
+
module DbMagic
|
4
|
+
|
4
5
|
def db_magic(opt = {})
|
5
6
|
# Make sure we could use our connections management here
|
6
7
|
hijack_connection!
|
@@ -13,8 +14,14 @@ module DbCharmer
|
|
13
14
|
|
14
15
|
# Set up slaves pool
|
15
16
|
opt[:slaves] ||= []
|
17
|
+
opt[:slaves] = [ opt[:slaves] ].flatten
|
16
18
|
opt[:slaves] << opt[:slave] if opt[:slave]
|
17
|
-
|
19
|
+
|
20
|
+
# Forced reads are enabled for all models by default, could be disabled by the user
|
21
|
+
forced_slave_reads = opt.has_key?(:force_slave_reads) ? opt[:force_slave_reads] : true
|
22
|
+
|
23
|
+
# Setup all the slaves related magic if needed
|
24
|
+
setup_slaves_magic(opt[:slaves], forced_slave_reads, should_exist) if opt[:slaves].any?
|
18
25
|
|
19
26
|
# Setup inheritance magic
|
20
27
|
setup_children_magic(opt)
|
@@ -39,7 +46,7 @@ module DbCharmer
|
|
39
46
|
|
40
47
|
def setup_sharding_magic(config)
|
41
48
|
# Add sharding-specific methods
|
42
|
-
self.extend(DbCharmer::Sharding
|
49
|
+
self.extend(DbCharmer::ActiveRecord::Sharding)
|
43
50
|
|
44
51
|
# Get configuration
|
45
52
|
name = config[:sharded_connection] or raise ArgumentError, "No :sharded_connection!"
|
@@ -55,15 +62,18 @@ module DbCharmer
|
|
55
62
|
self.db_charmer_default_connection = conn
|
56
63
|
end
|
57
64
|
|
58
|
-
def setup_slaves_magic(slaves, should_exist = true)
|
65
|
+
def setup_slaves_magic(slaves, force_slave_reads, should_exist = true)
|
59
66
|
self.db_charmer_slaves = slaves.collect do |slave|
|
60
67
|
coerce_to_connection_proxy(slave, should_exist)
|
61
68
|
end
|
62
69
|
|
63
|
-
self.
|
64
|
-
|
65
|
-
self.extend(DbCharmer::
|
70
|
+
self.db_charmer_force_slave_reads = force_slave_reads
|
71
|
+
|
72
|
+
self.extend(DbCharmer::ActiveRecord::FinderOverrides::ClassMethods)
|
73
|
+
self.send(:include, DbCharmer::ActiveRecord::FinderOverrides::InstanceMethods)
|
74
|
+
self.extend(DbCharmer::ActiveRecord::MultiDbProxy::MasterSlaveClassMethods)
|
66
75
|
end
|
76
|
+
|
67
77
|
end
|
68
78
|
end
|
69
79
|
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module DbCharmer
|
2
|
+
module ActiveRecord
|
3
|
+
module FinderOverrides
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
SLAVE_METHODS = [ :find_by_sql, :count_by_sql, :calculate ]
|
7
|
+
MASTER_METHODS = [ :update, :create, :delete, :destroy, :delete_all, :destroy_all, :update_all, :update_counters ]
|
8
|
+
|
9
|
+
SLAVE_METHODS.each do |slave_method|
|
10
|
+
class_eval <<-EOF, __FILE__, __LINE__ + 1
|
11
|
+
def #{slave_method}(*args, &block)
|
12
|
+
first_level_on_slave do
|
13
|
+
super(*args, &block)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
EOF
|
17
|
+
end
|
18
|
+
|
19
|
+
MASTER_METHODS.each do |master_method|
|
20
|
+
class_eval <<-EOF, __FILE__, __LINE__ + 1
|
21
|
+
def #{master_method}(*args, &block)
|
22
|
+
on_master do
|
23
|
+
super(*args, &block)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
EOF
|
27
|
+
end
|
28
|
+
|
29
|
+
def find(*args, &block)
|
30
|
+
options = args.last
|
31
|
+
if options.is_a?(Hash) && options[:lock]
|
32
|
+
on_master { super(*args, &block) }
|
33
|
+
else
|
34
|
+
super(*args, &block)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def first_level_on_slave
|
41
|
+
first_level = db_charmer_top_level_connection? && on_master.connection.open_transactions.zero?
|
42
|
+
if first_level && db_charmer_force_slave_reads?
|
43
|
+
on_slave { yield }
|
44
|
+
else
|
45
|
+
yield
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
module InstanceMethods
|
52
|
+
def reload(*args, &block)
|
53
|
+
self.class.on_master do
|
54
|
+
super(*args, &block)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module DbCharmer
|
2
|
+
module ActiveRecord
|
3
|
+
module Migration
|
4
|
+
module MultiDbMigrations
|
5
|
+
|
6
|
+
def self.extended(base)
|
7
|
+
class << base
|
8
|
+
alias_method_chain :migrate, :db_wrapper
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
@@multi_db_names = {}
|
13
|
+
def multi_db_names
|
14
|
+
@@multi_db_names[self.name] || @@multi_db_names['ActiveRecord::Migration']
|
15
|
+
end
|
16
|
+
|
17
|
+
def multi_db_names=(names)
|
18
|
+
@@multi_db_names[self.name] = names
|
19
|
+
end
|
20
|
+
|
21
|
+
def migrate_with_db_wrapper(direction)
|
22
|
+
if names = multi_db_names
|
23
|
+
names.each do |multi_db_name|
|
24
|
+
on_db(multi_db_name) do
|
25
|
+
migrate_without_db_wrapper(direction)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
else
|
29
|
+
migrate_without_db_wrapper(direction)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def on_db(db_name)
|
34
|
+
name = db_name.is_a?(Hash) ? db_name[:connection_name] : db_name.inspect
|
35
|
+
announce "Switching connection to #{name}"
|
36
|
+
# Switch connection
|
37
|
+
old_proxy = ::ActiveRecord::Base.db_charmer_connection_proxy
|
38
|
+
db_name = nil if db_name == :default
|
39
|
+
::ActiveRecord::Base.switch_connection_to(db_name, DbCharmer.connections_should_exist?)
|
40
|
+
# Yield the block
|
41
|
+
yield
|
42
|
+
ensure
|
43
|
+
# Switch it back
|
44
|
+
::ActiveRecord::Base.verify_active_connections!
|
45
|
+
announce "Switching connection back"
|
46
|
+
::ActiveRecord::Base.switch_connection_to(old_proxy)
|
47
|
+
end
|
48
|
+
|
49
|
+
def db_magic(opts = {})
|
50
|
+
# Collect connections from all possible options
|
51
|
+
conns = [ opts[:connection], opts[:connections] ]
|
52
|
+
conns << shard_connections(opts[:sharded_connection]) if opts[:sharded_connection]
|
53
|
+
|
54
|
+
# Get a unique set of connections
|
55
|
+
conns = conns.flatten.compact.uniq
|
56
|
+
raise ArgumentError, "No connection name - no magic!" unless conns.any?
|
57
|
+
|
58
|
+
# Save connections
|
59
|
+
self.multi_db_names = conns
|
60
|
+
end
|
61
|
+
|
62
|
+
# Return a list of connections to shards in a sharded connection
|
63
|
+
def shard_connections(conn_name)
|
64
|
+
conn = DbCharmer::Sharding.sharded_connection(conn_name)
|
65
|
+
conn.shard_connections
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module DbCharmer
|
2
|
+
module ActiveRecord
|
3
|
+
module MultiDbProxy
|
4
|
+
# Simple proxy class that switches connections and then proxies all the calls
|
5
|
+
# This class is used to implement chained on_db calls
|
6
|
+
class OnDbProxy < BlankSlate
|
7
|
+
def initialize(proxy_target, slave)
|
8
|
+
@proxy_target = proxy_target
|
9
|
+
@slave = slave
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def method_missing(meth, *args, &block)
|
15
|
+
# Switch connection and proxy the method call
|
16
|
+
@proxy_target.on_db(@slave) do |proxy_target|
|
17
|
+
res = proxy_target.__send__(meth, *args, &block)
|
18
|
+
|
19
|
+
# If result is a scope/association, return a new proxy for it, otherwise return the result itself
|
20
|
+
(res.proxy?) ? OnDbProxy.new(res, @slave) : res
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module ClassMethods
|
26
|
+
def on_db(con, proxy_target = nil)
|
27
|
+
proxy_target ||= self
|
28
|
+
|
29
|
+
# Chain call
|
30
|
+
return OnDbProxy.new(proxy_target, con) unless block_given?
|
31
|
+
|
32
|
+
# Block call
|
33
|
+
begin
|
34
|
+
self.db_charmer_connection_level += 1
|
35
|
+
old_proxy = db_charmer_connection_proxy
|
36
|
+
switch_connection_to(con, DbCharmer.connections_should_exist?)
|
37
|
+
yield(proxy_target)
|
38
|
+
ensure
|
39
|
+
switch_connection_to(old_proxy)
|
40
|
+
self.db_charmer_connection_level -= 1
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
module InstanceMethods
|
46
|
+
def on_db(con, proxy_target = nil, &block)
|
47
|
+
proxy_target ||= self
|
48
|
+
self.class.on_db(con, proxy_target, &block)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
module MasterSlaveClassMethods
|
53
|
+
def on_slave(con = nil, proxy_target = nil, &block)
|
54
|
+
con ||= db_charmer_random_slave
|
55
|
+
raise ArgumentError, "No slaves found in the class and no slave connection given" unless con
|
56
|
+
on_db(con, proxy_target, &block)
|
57
|
+
end
|
58
|
+
|
59
|
+
def on_master(proxy_target = nil, &block)
|
60
|
+
on_db(db_charmer_default_connection, proxy_target, &block)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module DbCharmer
|
2
|
+
module ActiveRecord
|
3
|
+
module NamedScope
|
4
|
+
module ScopeProxy
|
5
|
+
|
6
|
+
def proxy?
|
7
|
+
true
|
8
|
+
end
|
9
|
+
|
10
|
+
def on_db(con, proxy_target = nil, &block)
|
11
|
+
proxy_target ||= self
|
12
|
+
proxy_scope.on_db(con, proxy_target, &block)
|
13
|
+
end
|
14
|
+
|
15
|
+
def on_slave(con = nil, &block)
|
16
|
+
proxy_scope.on_slave(con, self, &block)
|
17
|
+
end
|
18
|
+
|
19
|
+
def on_master(&block)
|
20
|
+
proxy_scope.on_master(self, &block)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|