yam-db-charmer 1.7.4.10 → 1.7.4.13
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/db_charmer/active_record/class_attributes.rb +4 -0
- data/lib/db_charmer/active_record/connection_switching.rb +2 -2
- data/lib/db_charmer/active_record/db_magic.rb +11 -0
- data/lib/db_charmer/active_record/multi_db_proxy.rb +1 -1
- data/lib/db_charmer/active_record/reflection.rb +23 -0
- data/lib/db_charmer/active_record/sharding.rb +9 -7
- data/lib/db_charmer/sharding/method/db_block_group_map_base.rb +31 -9
- data/lib/db_charmer/sharding/method/db_block_schema_map.rb +23 -11
- data/lib/db_charmer/sharding.rb +5 -1
- data/lib/db_charmer/version.rb +1 -1
- data/lib/db_charmer.rb +1 -0
- data/lib/tasks/databases.rake +37 -4
- data/lib/tasks/test.rake +224 -9
- metadata +5 -4
@@ -20,6 +20,10 @@ module DbCharmer
|
|
20
20
|
@@db_charmer_connection_proxies[self.name]
|
21
21
|
end
|
22
22
|
|
23
|
+
def reset_connection_proxies
|
24
|
+
@@db_charmer_connection_proxies = {}
|
25
|
+
end
|
26
|
+
|
23
27
|
#-----------------------------------------------------------------------------
|
24
28
|
@@db_charmer_default_connections = {}
|
25
29
|
def db_charmer_default_connection=(conn)
|
@@ -36,8 +36,8 @@ module DbCharmer
|
|
36
36
|
class << self
|
37
37
|
# Make sure we check our accessors before going to the default connection retrieval method
|
38
38
|
def connection_with_magic
|
39
|
-
if db_charmer_allocated_shard_connection
|
40
|
-
return coerce_to_connection_proxy(db_charmer_allocated_shard_connection, true)
|
39
|
+
if db_charmer_allocated_shard_connection && self.get_shard_strategy == :horizontal
|
40
|
+
return coerce_to_connection_proxy(db_charmer_allocated_shard_connection[:config], true)
|
41
41
|
end
|
42
42
|
db_charmer_remapped_connection || db_charmer_connection_proxy || connection_without_magic
|
43
43
|
end
|
@@ -52,6 +52,17 @@ module DbCharmer
|
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
55
|
+
def exist_pg_database?(server_config, db_name)
|
56
|
+
config_no_db_name = server_config.clone
|
57
|
+
config_no_db_name['database'] = nil
|
58
|
+
establish_connection(config_no_db_name)
|
59
|
+
server_connection = ::ActiveRecord::Base.connection
|
60
|
+
sql = "SELECT datname FROM pg_database WHERE datname='#{db_name}'"
|
61
|
+
existing_dbs = server_connection.execute(sql)
|
62
|
+
server_connection.disconnect!
|
63
|
+
existing_dbs.first ? true : false
|
64
|
+
end
|
65
|
+
|
55
66
|
private
|
56
67
|
|
57
68
|
def setup_children_magic(opt)
|
@@ -36,7 +36,7 @@ module DbCharmer
|
|
36
36
|
if self.respond_to? :set_schema_table_name_prefix
|
37
37
|
self.set_schema_table_name_prefix(con)
|
38
38
|
end
|
39
|
-
|
39
|
+
|
40
40
|
# Chain call
|
41
41
|
#Rails.logger.debug "MultiDbProxy#on_db: con=#{con} proxy_target=#{proxy_target}"
|
42
42
|
return OnDbProxy.new(proxy_target, con) unless block_given?
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Reflection
|
3
|
+
class AssociationReflection < MacroReflection
|
4
|
+
def table_name
|
5
|
+
klass.switch_connection_to(klass.db_charmer_allocated_shard_connection[:config]) if sharded_reflection?
|
6
|
+
@table_name = klass.table_name
|
7
|
+
end
|
8
|
+
|
9
|
+
def quoted_table_name
|
10
|
+
klass.switch_connection_to(klass.db_charmer_allocated_shard_connection[:config]) if sharded_reflection?
|
11
|
+
@quoted_table_name = klass.quoted_table_name
|
12
|
+
end
|
13
|
+
|
14
|
+
def sharded_reflection?
|
15
|
+
# TODO: put this logic in WF
|
16
|
+
if klass.db_charmer_allocated_shard_connection && klass.db_charmer_extended
|
17
|
+
return true;
|
18
|
+
end
|
19
|
+
return false
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -15,14 +15,16 @@ module DbCharmer
|
|
15
15
|
# The block passed to this function should return an object responding to key_field
|
16
16
|
def create_with_allocated_shard(key_field = :id, &block)
|
17
17
|
sharder = sharded_connection.sharder
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
old_conn = self.db_charmer_allocated_shard_connection
|
19
|
+
self.db_charmer_allocated_shard_connection ||= begin
|
20
|
+
group = sharder.least_loaded_group
|
21
|
+
shard_info = sharder.shard_info_by_group_id(group.id)
|
22
|
+
{:group => group, :shard_info => shard_info, :config => sharder.shard_connection_config(shard_info, group.id)}
|
23
|
+
end
|
21
24
|
# Set global shard connection obj
|
22
|
-
self.db_charmer_allocated_shard_connection
|
23
|
-
key_obj
|
24
|
-
self.db_charmer_allocated_shard_connection =
|
25
|
-
sharder.allocate_new_block_for_key_on_group(key_obj.send(key_field), group)
|
25
|
+
key_obj = on_db(self.db_charmer_allocated_shard_connection[:config], nil, &block)
|
26
|
+
sharder.allocate_new_block_for_key_on_group(key_obj.send(key_field), self.db_charmer_allocated_shard_connection[:group]) if old_conn.nil?
|
27
|
+
self.db_charmer_allocated_shard_connection = old_conn
|
26
28
|
key_obj
|
27
29
|
end
|
28
30
|
|
@@ -201,7 +201,7 @@ module DbCharmer
|
|
201
201
|
"DbCharmer::Sharding::Method::DbBlockSchemaMap::Shard".classify.constantize
|
202
202
|
end
|
203
203
|
end
|
204
|
-
|
204
|
+
|
205
205
|
def shard_connections
|
206
206
|
# Find all groups
|
207
207
|
prepare_shard_models
|
@@ -238,24 +238,46 @@ module DbCharmer
|
|
238
238
|
|
239
239
|
def create_shard_database(shard)
|
240
240
|
conn_config = shard_connection_config_no_dbname(shard)
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
existing_dbs = ::ActiveRecord::Base.connection.execute(sql)
|
245
|
-
unless existing_dbs.first
|
241
|
+
unless ::ActiveRecord::Base.exist_pg_database?(conn_config, shard.db_name)
|
242
|
+
old_proxy = ::ActiveRecord::Base.db_charmer_connection_proxy
|
243
|
+
::ActiveRecord::Base.establish_connection(conn_config)
|
246
244
|
::ActiveRecord::Base.connection.create_database(shard.db_name, conn_config)
|
245
|
+
::ActiveRecord::Base.connection.disconnect!
|
246
|
+
::ActiveRecord::Base.switch_connection_to(old_proxy)
|
247
247
|
end
|
248
|
-
::ActiveRecord::Base.switch_connection_to(old_proxy)
|
249
248
|
end
|
250
|
-
|
249
|
+
|
251
250
|
def drop_shard_database(shard)
|
252
251
|
conn_config = shard_connection_config_no_dbname(shard)
|
252
|
+
conn_config_with_dbname = conn_config.clone.merge(:database => shard.db_name)
|
253
|
+
kill_connections(conn_config_with_dbname, true)
|
253
254
|
old_proxy = ::ActiveRecord::Base.db_charmer_connection_proxy
|
254
|
-
::ActiveRecord::Base.
|
255
|
+
::ActiveRecord::Base.establish_connection(conn_config)
|
256
|
+
puts "Dropping shard database: #{shard.inspect}"
|
255
257
|
::ActiveRecord::Base.connection.drop_database(shard.db_name)
|
258
|
+
::ActiveRecord::Base.connection.disconnect!
|
256
259
|
::ActiveRecord::Base.switch_connection_to(old_proxy)
|
257
260
|
end
|
258
261
|
|
262
|
+
# sets environment variables to be used by an exec'd psql process
|
263
|
+
def set_psql_env(config)
|
264
|
+
ENV['PGHOST'] = config[:host] if config[:host]
|
265
|
+
ENV['PGPORT'] = config[:port].to_s if config[:port]
|
266
|
+
ENV['PGPASSWORD'] = config[:password].to_s if config[:password]
|
267
|
+
ENV['PGUSER'] = config[:username].to_s if config[:username]
|
268
|
+
end
|
269
|
+
|
270
|
+
# kills all connections to a database
|
271
|
+
def kill_connections(config, verbose = false)
|
272
|
+
set_psql_env(config)
|
273
|
+
proclist_sql = "select pg_terminate_backend(procpid) from pg_stat_activity where datname='#{config[:database]}';"
|
274
|
+
cmd = "psql -U '#{config[:username]}' -c \"#{proclist_sql}\" -t"
|
275
|
+
|
276
|
+
puts "Killing connections to: #{config[:database]}"
|
277
|
+
puts cmd if verbose
|
278
|
+
system(cmd)
|
279
|
+
end
|
280
|
+
|
259
281
|
def drop_all_shard_databases
|
260
282
|
prepare_shard_models
|
261
283
|
shard_class.all.each do |shard|
|
@@ -51,7 +51,7 @@ module DbCharmer
|
|
51
51
|
# They do not expose configs so we hack in and get the instance var
|
52
52
|
# FIXME: Find a better way, maybe move config method to our ar extenstions
|
53
53
|
slave_host = shard.respond_to?(:db_slave_host) ? shard.db_slave_host : ''
|
54
|
-
connection.config.clone.merge(
|
54
|
+
connection.instance_variable_get("@config").clone.merge(
|
55
55
|
# Name for the connection factory
|
56
56
|
:connection_name => connection_name,
|
57
57
|
# Connection params
|
@@ -136,6 +136,7 @@ module DbCharmer
|
|
136
136
|
end
|
137
137
|
|
138
138
|
Group.switch_connection_to(old_connection)
|
139
|
+
group
|
139
140
|
end
|
140
141
|
|
141
142
|
def shard_connections
|
@@ -159,40 +160,51 @@ module DbCharmer
|
|
159
160
|
# To be mixed in AR#base.
|
160
161
|
# Allows a schema name to be set at the same time a db connection is selected.
|
161
162
|
module SchemaTableNamePrefix
|
163
|
+
|
162
164
|
def set_schema_table_name_prefix(con)
|
163
165
|
|
164
166
|
#Rails.logger.debug "set_schema_table_name_prefix: self=#{self} @dbcharmer_table_name_prefix=#{@dbcharmer_table_name_prefix}"
|
165
167
|
if con.is_a?(Hash) && con[:schema_name]
|
166
168
|
new_prefix = con[:schema_name] + '.'
|
167
|
-
if self.to_s!=
|
168
|
-
@orig_table_name =
|
169
|
+
if @orig_table_name.blank? && self.to_s != "ActiveRecord::Base"
|
170
|
+
@orig_table_name = orig_table_name
|
169
171
|
end
|
170
172
|
else
|
171
173
|
new_prefix = '' if self.to_s=='ActiveRecord::Base' # this is for migrations
|
172
174
|
end
|
173
|
-
|
174
|
-
# remove the old dbcharmer prefix first
|
175
|
-
if !@dbcharmer_table_name_prefix.blank? && @dbcharmer_table_name_prefix != new_prefix
|
176
|
-
self.table_name_prefix = self.table_name_prefix.gsub(/#{Regexp.escape(@dbcharmer_table_name_prefix)}/, '')
|
177
|
-
end
|
178
175
|
|
179
176
|
# Set the new table_name_prefix
|
180
177
|
if new_prefix && @dbcharmer_table_name_prefix != new_prefix
|
178
|
+
# remove the old dbcharmer prefix first
|
179
|
+
if !@dbcharmer_table_name_prefix.blank?
|
180
|
+
self.table_name_prefix = self.table_name_prefix.gsub(/#{Regexp.escape(@dbcharmer_table_name_prefix)}/, '')
|
181
|
+
end
|
182
|
+
|
181
183
|
@dbcharmer_table_name_prefix = new_prefix
|
182
|
-
self.table_name_prefix =
|
184
|
+
self.table_name_prefix = new_prefix
|
183
185
|
|
184
186
|
# Reset all forms of table_name that were memoized in Rails.
|
185
187
|
# Don't do it in the context of migrations where the current class
|
186
188
|
# is AR::Base and there is no actual table name.
|
187
189
|
unless self.to_s=='ActiveRecord::Base'
|
188
190
|
#Rails.logger.debug "set_schema_table_name_prefix: resetting"
|
189
|
-
reset_cached_table_name(
|
191
|
+
reset_cached_table_name(orig_table_name)
|
190
192
|
end
|
191
193
|
end
|
192
194
|
end
|
193
195
|
|
194
196
|
def orig_table_name
|
195
|
-
|
197
|
+
# Polymorphic active record classes won't have orig_table_name defined and we'll add double prefixes without this logic, because table_name
|
198
|
+
# usees parent class tables
|
199
|
+
if self.superclass && self.superclass.respond_to?(:orig_table_name) && self.superclass.orig_table_name
|
200
|
+
self.superclass.orig_table_name
|
201
|
+
elsif self == ::ActiveRecord::Base
|
202
|
+
nil
|
203
|
+
elsif @orig_table_name
|
204
|
+
@orig_table_name
|
205
|
+
else
|
206
|
+
self.table_name
|
207
|
+
end
|
196
208
|
end
|
197
209
|
|
198
210
|
# Rails memoizes table_name and table_name_prefix at the time the models are loaded.
|
data/lib/db_charmer/sharding.rb
CHANGED
@@ -18,7 +18,7 @@ module DbCharmer
|
|
18
18
|
|
19
19
|
# name is the is the sharded_connection name passed to db_magic in the model
|
20
20
|
def self.sharded_connection(name)
|
21
|
-
@@sharded_connections[name] or raise ArgumentError, "Invalid sharded connection name!"
|
21
|
+
@@sharded_connections[name.to_sym] or raise ArgumentError, "Invalid sharded connection name!"
|
22
22
|
end
|
23
23
|
|
24
24
|
# Return the DbCharmer::Sharding::Connection that matches name
|
@@ -42,5 +42,9 @@ module DbCharmer
|
|
42
42
|
return unless sharder
|
43
43
|
sharder.shard_class.where(:id => connection_config[:shard_id]).first
|
44
44
|
end
|
45
|
+
|
46
|
+
def self.reset_connections
|
47
|
+
@@sharded_connections = {}
|
48
|
+
end
|
45
49
|
end
|
46
50
|
end
|
data/lib/db_charmer/version.rb
CHANGED
data/lib/db_charmer.rb
CHANGED
@@ -129,6 +129,7 @@ ActiveRecord::Base.send(:include, DbCharmer::ActiveRecord::MultiDbProxy::Instanc
|
|
129
129
|
|
130
130
|
# Enable connection proxy for relations
|
131
131
|
if DbCharmer.rails3?
|
132
|
+
require 'db_charmer/active_record/reflection'
|
132
133
|
require 'db_charmer/rails3/active_record/relation_method'
|
133
134
|
require 'db_charmer/rails3/active_record/relation/arel_engine'
|
134
135
|
require 'db_charmer/rails3/active_record/relation/connection_routing'
|
data/lib/tasks/databases.rake
CHANGED
@@ -70,18 +70,21 @@ namespace :db_charmer do
|
|
70
70
|
puts "This task only modifies local databases. #{config['database']} is on a remote host."
|
71
71
|
end
|
72
72
|
end
|
73
|
+
|
73
74
|
end
|
74
75
|
|
75
76
|
def drop_core_and_sub_database(config)
|
76
77
|
exit unless Rails.env=='test'
|
78
|
+
puts "Dropping #{config['database']}"
|
77
79
|
drop_database(config)
|
78
80
|
config.each_value do | sub_config |
|
79
81
|
next unless sub_config.is_a?(Hash)
|
80
82
|
next unless sub_config['database']
|
81
83
|
begin
|
84
|
+
puts "Dropping #{config['database']}"
|
82
85
|
drop_database(sub_config)
|
83
86
|
rescue => e
|
84
|
-
$stderr.puts "#{e.to_s}, #{config['database']} not
|
87
|
+
$stderr.puts "#{e.to_s}, #{config['database']} does not exist"
|
85
88
|
end
|
86
89
|
end
|
87
90
|
end
|
@@ -89,9 +92,28 @@ end
|
|
89
92
|
# find all schema sharded databases and drop them
|
90
93
|
def drop_schema_shard_databases(config)
|
91
94
|
exit unless Rails.env=='test'
|
92
|
-
|
93
95
|
DbCharmer.env = 'test'
|
96
|
+
|
97
|
+
# remember the sharder config so that we can register it again
|
98
|
+
# after resetting all dbcharmer connections
|
99
|
+
sharding_connection_config = {}
|
100
|
+
config.each do |name, sub_config|
|
101
|
+
next unless sub_config.is_a?(Hash)
|
102
|
+
sharder_name = sub_config['sharder_name']
|
103
|
+
if sharder_name
|
104
|
+
sharding_connection = DbCharmer::Sharding.sharded_connection(sharder_name)
|
105
|
+
sharding_connection_config[sharder_name] = sharding_connection.config if sharding_connection
|
106
|
+
break if sharding_connection_config
|
107
|
+
end
|
108
|
+
end
|
109
|
+
raise 'No sharder found' unless sharding_connection_config.size>0
|
110
|
+
|
111
|
+
# Reset all DbCharmer connection to make sure to switch to test environment
|
112
|
+
# (connections were initialized for dev env in db:test:prepare)
|
94
113
|
DbCharmer::ConnectionFactory.reset!
|
114
|
+
DbCharmer::Sharding.reset_connections
|
115
|
+
ActiveRecord::Base.reset_connection_proxies
|
116
|
+
|
95
117
|
|
96
118
|
config.each do |name, sub_config|
|
97
119
|
next unless sub_config.is_a?(Hash)
|
@@ -99,10 +121,21 @@ def drop_schema_shard_databases(config)
|
|
99
121
|
|
100
122
|
# find the database connection for the schema admin db
|
101
123
|
next unless sub_config['shard_db_name_prefix']
|
102
|
-
|
124
|
+
|
125
|
+
# Register sharder again
|
126
|
+
sharder_name = sub_config['sharder_name']
|
127
|
+
scc = sharding_connection_config[sharder_name]
|
128
|
+
next unless scc
|
129
|
+
scc[:connection] = name
|
130
|
+
DbCharmer::Sharding.register_connection(scc)
|
131
|
+
connection = DbCharmer::Sharding.sharded_connection(sharder_name)
|
103
132
|
next unless connection
|
104
133
|
|
105
|
-
#
|
134
|
+
# Make sure the sharding admin database where we need to find the
|
135
|
+
# config for shards databases exist
|
136
|
+
next unless ::ActiveRecord::Base.exist_pg_database?(sub_config, sub_config['database'])
|
137
|
+
|
138
|
+
# iterate through entries in the shards_info table to find the
|
106
139
|
# databases that will be dropped
|
107
140
|
dbgm = DbCharmer::Sharding::Method::DbBlockSchemaMap.new(connection.config)
|
108
141
|
|
data/lib/tasks/test.rake
CHANGED
@@ -6,6 +6,7 @@ namespace :db_charmer do
|
|
6
6
|
task :prepare => ['db:abort_if_pending_migrations', 'db:load_config'] do |t|
|
7
7
|
old_rails_env = Rails.env
|
8
8
|
Rails.env = 'test'
|
9
|
+
DbCharmer.env = 'test'
|
9
10
|
unless ActiveRecord::Base.schema_format==:sql
|
10
11
|
abort "db_charmer can only reset databases using sql, check config.active_record.schema_format"
|
11
12
|
end
|
@@ -20,9 +21,23 @@ namespace :db_charmer do
|
|
20
21
|
end
|
21
22
|
|
22
23
|
task :dump_structure do
|
24
|
+
@saved_sharding_config = {}
|
25
|
+
|
23
26
|
::ActiveRecord::Base.configurations['development'].each_key do |name|
|
24
27
|
config = ::ActiveRecord::Base.configurations['development'][name]
|
25
28
|
next unless config.is_a?(Hash) && config['database']
|
29
|
+
|
30
|
+
if config['shard_db_name_prefix']
|
31
|
+
# dump schema for first sharded partition
|
32
|
+
@saved_sharding_config[name] = Sharding::ShardDatabaseConfigConverter.new(name, config)
|
33
|
+
cfg = @saved_sharding_config[name].dev_shard_db_config
|
34
|
+
puts "dev_shard_db_config=#{cfg.inspect}"
|
35
|
+
raise 'Cannot find shard configuration' unless cfg
|
36
|
+
dump_database_structure("#{name}_partition", cfg)
|
37
|
+
|
38
|
+
# Save the config for clone_structure
|
39
|
+
end
|
40
|
+
|
26
41
|
# Only connect to local databases
|
27
42
|
local_database?(config) { dump_database_structure(name, config) }
|
28
43
|
end
|
@@ -33,34 +48,64 @@ namespace :db_charmer do
|
|
33
48
|
|
34
49
|
# desc "Recreate the test databases from the development structure"
|
35
50
|
task :clone_structure => [ 'db_charmer:test:dump_structure', 'db_charmer:test:purge' ] do
|
51
|
+
# Databases have just been dropped, old connections are no longer valid
|
52
|
+
DbCharmer::Sharding.reset_connections
|
53
|
+
DbCharmer::ConnectionFactory.reset!
|
54
|
+
ActiveRecord::Base.reset_connection_proxies
|
55
|
+
|
56
|
+
# keep track of databases that have already been cloned
|
57
|
+
@cloned_db_names = {}
|
58
|
+
|
36
59
|
::ActiveRecord::Base.configurations['test'].each_key do |name|
|
37
60
|
config = ::ActiveRecord::Base.configurations['test'][name]
|
38
61
|
next unless config.is_a?(Hash) && config['database']
|
62
|
+
|
39
63
|
# Only connect to local databases
|
40
|
-
local_database?(config) { clone_database_structure(name, config) }
|
64
|
+
local_database?(config) { clone_database_structure(name, config, true) }
|
65
|
+
|
66
|
+
if config['shard_db_name_prefix']
|
67
|
+
# Create all partition databases and schemas.
|
68
|
+
# The number of desired databases and scemas is in config.
|
69
|
+
@saved_sharding_config[name].populate_test_shard(config)
|
70
|
+
@saved_sharding_config[name].each_database_shard_group do |partition_config|
|
71
|
+
puts "partition_config=#{partition_config.inspect}"
|
72
|
+
# Only connect to local databases
|
73
|
+
local_database?(partition_config) do
|
74
|
+
clone_database_structure("#{name}_partition", partition_config, false)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
delete_structure_file("#{name}_partition")
|
78
|
+
end
|
41
79
|
end
|
42
80
|
config = ::ActiveRecord::Base.configurations['test']
|
43
|
-
local_database?(config) { clone_database_structure('main', config) }
|
81
|
+
local_database?(config) { clone_database_structure('main', config, true) }
|
44
82
|
end
|
45
83
|
|
46
84
|
def dump_database_structure(name, config, origin_env='development')
|
47
85
|
return unless config.is_a?(Hash) && config['database']
|
48
|
-
|
86
|
+
|
87
|
+
structure_filename = structure_filename(name, origin_env)
|
49
88
|
case config["adapter"]
|
50
89
|
when /^(jdbc)?mysql/, "oci", "oracle"
|
51
90
|
ActiveRecord::Base.establish_connection(config)
|
52
91
|
File.open(structure_filename, "w+") { |f| f << ActiveRecord::Base.connection.structure_dump }
|
53
92
|
when /^(jdbc)?postgresql$/
|
54
93
|
ENV['PGHOST'] = config["host"] if config["host"]
|
55
|
-
|
94
|
+
if config["port"]
|
95
|
+
ENV['PGPORT'] = config["port"].to_s
|
96
|
+
port_option = "-p #{config["port"]}"
|
97
|
+
end
|
56
98
|
search_path = config["schema_search_path"]
|
57
99
|
unless search_path.blank?
|
58
100
|
search_path = search_path.split(",").map{|search_path| "--schema=#{search_path.strip}" }.join(" ")
|
59
101
|
end
|
60
|
-
cmd ="pg_dump -U #{config["username"]} -s -x -O -f
|
102
|
+
cmd ="pg_dump -U #{config["username"]} -s -x -O -f #{structure_filename} #{port_option} #{search_path} #{config['database']}"
|
61
103
|
puts cmd
|
62
104
|
`#{cmd}`
|
63
105
|
raise "Error dumping database" if $?.exitstatus == 1
|
106
|
+
unless config["schema_search_path"].blank?
|
107
|
+
replace_schema_name_in_file(structure_filename, structure_filename, config["schema_search_path"], 'public')
|
108
|
+
end
|
64
109
|
when /^(jdbc)?sqlite/
|
65
110
|
dbfile = config["database"] || config["dbfile"]
|
66
111
|
`sqlite3 #{dbfile} .schema > #{structure_filename}`
|
@@ -76,8 +121,18 @@ namespace :db_charmer do
|
|
76
121
|
end
|
77
122
|
end
|
78
123
|
|
79
|
-
def clone_database_structure(name, config, origin_env='development')
|
80
|
-
|
124
|
+
def clone_database_structure(name, config, delete_structure_file=false, origin_env='development')
|
125
|
+
unless @cloned_db_names[dbkey(config)]
|
126
|
+
@cloned_db_names[dbkey(config)] = true
|
127
|
+
else
|
128
|
+
puts "Skipping #{dbkey(config)} clone (already done)"
|
129
|
+
return
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
structure_filename = structure_filename(name, origin_env)
|
134
|
+
puts "clone_database_structure config=#{config.inspect} structure_filename=#{structure_filename}"
|
135
|
+
|
81
136
|
case config["adapter"]
|
82
137
|
when /^(jdbc)?mysql/
|
83
138
|
ActiveRecord::Base.establish_connection(:test)
|
@@ -87,11 +142,21 @@ namespace :db_charmer do
|
|
87
142
|
end
|
88
143
|
when /^(jdbc)?postgresql$/
|
89
144
|
ENV['PGHOST'] = config["host"] if config["host"]
|
90
|
-
|
145
|
+
if config["port"]
|
146
|
+
ENV['PGPORT'] = config["port"].to_s
|
147
|
+
port_option = "-p #{config["port"]}"
|
148
|
+
end
|
91
149
|
#ENV['PGPASSWORD'] = config["password"].to_s if config["password"]
|
92
|
-
|
150
|
+
schema_file = structure_filename
|
151
|
+
unless config["schema_search_path"].blank?
|
152
|
+
tmpfile = Tempfile.new('schema-')
|
153
|
+
replace_schema_name_in_file(structure_filename, tmpfile.path, 'public', config["schema_search_path"])
|
154
|
+
schema_file = tmpfile.path
|
155
|
+
end
|
156
|
+
cmd = "psql -U #{config["username"]} -f #{schema_file} #{port_option} #{config["database"]}"
|
93
157
|
puts cmd
|
94
158
|
`#{cmd}`
|
159
|
+
tmpfile.delete if tmpfile
|
95
160
|
when /^(jdbc)?sqlite/
|
96
161
|
dbfile = config["database"] || config["dbfile"]
|
97
162
|
`sqlite3 #{dbfile} < #{structure_filename}`
|
@@ -109,8 +174,158 @@ namespace :db_charmer do
|
|
109
174
|
else
|
110
175
|
raise "Task not supported by '#{config["adapter"]}'"
|
111
176
|
end
|
177
|
+
delete_structure_file(name) if delete_structure_file
|
178
|
+
end
|
112
179
|
|
180
|
+
def structure_filename(name, origin_env='development')
|
181
|
+
"#{Rails.root}/db/#{origin_env}_#{name}_structure.sql"
|
182
|
+
end
|
183
|
+
|
184
|
+
def delete_structure_file(name, origin_env='development')
|
185
|
+
structure_filename = structure_filename(name, origin_env)
|
186
|
+
puts "Deleting #{structure_filename}"
|
113
187
|
File.delete(structure_filename) if File.exist?(structure_filename)
|
114
188
|
end
|
189
|
+
|
190
|
+
def replace_schema_name_in_file(filename, tmpname, from, to)
|
191
|
+
sql = IO.read(filename)
|
192
|
+
sql = sql.gsub(/CREATE SCHEMA #{from}/, "CREATE SCHEMA #{to}")
|
193
|
+
sql = sql.gsub(/SET search_path = #{from}/, "SET search_path = #{to}")
|
194
|
+
File.open(tmpname, 'w') { |f| f.write(sql)}
|
195
|
+
end
|
196
|
+
|
197
|
+
def dbkey(config)
|
198
|
+
"#{config['database']}|#{config['host']}|#{config['port']}|#{config['schema_search_path']}"
|
199
|
+
end
|
115
200
|
end
|
116
201
|
end
|
202
|
+
|
203
|
+
# Since the sharding database config is not in database.yml but in
|
204
|
+
# the xxx_shards_info table, there needs to be a tool to generate
|
205
|
+
# a database config hash for the sharding database.
|
206
|
+
# This class can generate the hash for the dev sharding database
|
207
|
+
# and for the test sharding database, based on the content of the
|
208
|
+
# dev shards_info table.
|
209
|
+
module Sharding
|
210
|
+
class ShardDatabaseConfigConverter
|
211
|
+
|
212
|
+
attr_reader :dev_shard_db_config
|
213
|
+
|
214
|
+
# dev_sharding_config: db settings of the dev admin database (from database.yml)
|
215
|
+
def initialize(name, dev_sharding_config)
|
216
|
+
@name = name
|
217
|
+
@dev_sharding_config = dev_sharding_config
|
218
|
+
@dev_shard_db_config = generate_dev_shard_db_config # settings of the sharding database
|
219
|
+
@test_shards = []
|
220
|
+
raise "Can't determine shard database configuration." unless @dev_shard_db_config
|
221
|
+
end
|
222
|
+
|
223
|
+
# Inserts shard and group records in the test admin database.
|
224
|
+
# Saves the db settings hash for the test sharding database.
|
225
|
+
# test_sharding_config: db settings of the test admin database (from database.yml)
|
226
|
+
def populate_test_shard(test_sharding_config)
|
227
|
+
@test_sharding_config = test_sharding_config
|
228
|
+
create_test_shard_and_group
|
229
|
+
end
|
230
|
+
|
231
|
+
def each_database_shard_group
|
232
|
+
@test_shards.each do |shard|
|
233
|
+
shard.groups.each do |group|
|
234
|
+
cfg = { 'host' => shard.db_host,
|
235
|
+
'port' => shard.db_port,
|
236
|
+
'username' => shard.db_user,
|
237
|
+
'database' => shard.db_name,
|
238
|
+
'adapter' => @test_sharding_config['adapter'],
|
239
|
+
'schema_search_path' => @test_sharder.schema_name(shard.schema_name_prefix, group.id)
|
240
|
+
}
|
241
|
+
yield cfg
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
private
|
247
|
+
|
248
|
+
def generate_dev_shard_db_config
|
249
|
+
if @dev_sharding_config['connection_name']
|
250
|
+
conn = DbCharmer::Sharding.sharded_connection_by_connection_name(@dev_sharding_config['connection_name'])
|
251
|
+
elsif @dev_sharding_config['sharder_name']
|
252
|
+
conn = DbCharmer::Sharding.sharded_connection(@dev_sharding_config['sharder_name'])
|
253
|
+
else
|
254
|
+
puts "Can't find connection_name or sharder_name in database config"
|
255
|
+
return
|
256
|
+
end
|
257
|
+
@sharding_connection_config = conn.config
|
258
|
+
sharder = conn.sharder
|
259
|
+
group = sharder.group_class.first
|
260
|
+
shard = group.shard
|
261
|
+
@schema_name_prefix = shard.schema_name_prefix
|
262
|
+
cfg = { 'host' => shard.db_host,
|
263
|
+
'port' => shard.db_port,
|
264
|
+
'username' => shard.db_user,
|
265
|
+
'database' => shard.db_name,
|
266
|
+
'adapter' => @dev_sharding_config['adapter'],
|
267
|
+
'schema_search_path' => sharder.schema_name(@schema_name_prefix, group.id)
|
268
|
+
}
|
269
|
+
end
|
270
|
+
|
271
|
+
def generate_test_shard_db_config
|
272
|
+
return unless @test_sharding_config && @test_shard && @test_group
|
273
|
+
schema_name_prefix = @test_shard.schema_name_prefix
|
274
|
+
test_group_id = @test_group.id
|
275
|
+
cfg = { 'host' => @test_shard.db_host,
|
276
|
+
'port' => @test_shard.db_port,
|
277
|
+
'username' => @test_shard.db_user,
|
278
|
+
'database' => @test_shard.db_name,
|
279
|
+
'adapter' => @test_sharding_config['adapter'],
|
280
|
+
'schema_search_path' => @test_sharder.schema_name(schema_name_prefix, test_group_id)
|
281
|
+
}
|
282
|
+
end
|
283
|
+
|
284
|
+
def create_test_shard_and_group
|
285
|
+
return unless @test_sharding_config && @sharding_connection_config
|
286
|
+
|
287
|
+
# Reset database connection and sharder in test environment
|
288
|
+
@sharding_connection_config[:connection] = @name
|
289
|
+
DbCharmer::Sharding.register_connection(@sharding_connection_config)
|
290
|
+
conn = DbCharmer::Sharding.sharded_connection(@sharding_connection_config[:name])
|
291
|
+
@test_sharder = conn.sharder
|
292
|
+
@test_sharder.shard_class.establish_connection(@test_sharding_config)
|
293
|
+
@test_sharder.group_class.establish_connection(@test_sharding_config)
|
294
|
+
|
295
|
+
num_shards = @test_sharding_config['num_shard_databases'] || 1
|
296
|
+
num_groups = @test_sharding_config['num_shard_partitions'] || 1
|
297
|
+
|
298
|
+
1.upto num_shards do |ns|
|
299
|
+
test_shard = @test_sharder.shard_class.create do |s|
|
300
|
+
s.db_host = @test_sharding_config['host']
|
301
|
+
s.db_port = @test_sharding_config['port']
|
302
|
+
s.db_user = @test_sharding_config['username']
|
303
|
+
s.db_pass = @test_sharding_config['password'] || ''
|
304
|
+
s.db_name = test_database_name(ns)
|
305
|
+
s.schema_name_prefix = @schema_name_prefix
|
306
|
+
end
|
307
|
+
|
308
|
+
@test_sharder.create_shard_database(test_shard)
|
309
|
+
|
310
|
+
1.upto num_groups do |np|
|
311
|
+
@test_group = @test_sharder.group_class.create do |group|
|
312
|
+
group.shard_id = test_shard.id
|
313
|
+
group.open = true
|
314
|
+
group.enabled = true
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
@test_shards << test_shard
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
def test_database_name(ns)
|
323
|
+
dev_db_name_prefix = Regexp.escape(@dev_sharding_config['shard_db_name_prefix'])
|
324
|
+
test_db_name_prefix = @test_sharding_config['shard_db_name_prefix']
|
325
|
+
s = @dev_shard_db_config['database'].gsub(/#{dev_db_name_prefix}/, test_db_name_prefix)
|
326
|
+
s + "_#{ns}"
|
327
|
+
end
|
328
|
+
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: yam-db-charmer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 109
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 1
|
8
8
|
- 7
|
9
9
|
- 4
|
10
|
-
-
|
11
|
-
version: 1.7.4.
|
10
|
+
- 13
|
11
|
+
version: 1.7.4.13
|
12
12
|
platform: ruby
|
13
13
|
authors:
|
14
14
|
- Oleksiy Kovyrin
|
@@ -17,7 +17,7 @@ autorequire:
|
|
17
17
|
bindir: bin
|
18
18
|
cert_chain: []
|
19
19
|
|
20
|
-
date: 2012-10-
|
20
|
+
date: 2012-10-24 00:00:00 -07:00
|
21
21
|
default_executable:
|
22
22
|
dependencies:
|
23
23
|
- !ruby/object:Gem::Dependency
|
@@ -118,6 +118,7 @@ files:
|
|
118
118
|
- lib/db_charmer/active_record/db_magic.rb
|
119
119
|
- lib/db_charmer/active_record/migration/multi_db_migrations.rb
|
120
120
|
- lib/db_charmer/active_record/multi_db_proxy.rb
|
121
|
+
- lib/db_charmer/active_record/reflection.rb
|
121
122
|
- lib/db_charmer/active_record/sharding.rb
|
122
123
|
- lib/db_charmer/connection_factory.rb
|
123
124
|
- lib/db_charmer/connection_proxy.rb
|