yam-db-charmer 1.7.4.10 → 1.7.4.13
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/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
|