db-charmer 1.6.19 → 1.7.0.pre1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|