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.
@@ -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
- group = sharder.least_loaded_group
19
- shard_info = sharder.shard_info_by_group_id(group.id)
20
- conn = sharder.shard_connection_config(shard_info, group.id)
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 = conn
23
- key_obj = on_db(conn, nil, &block)
24
- self.db_charmer_allocated_shard_connection = nil
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
- old_proxy = ::ActiveRecord::Base.db_charmer_connection_proxy
242
- ::ActiveRecord::Base.switch_connection_to(conn_config)
243
- sql = "SELECT datname FROM pg_database WHERE datname='#{shard.db_name}'"
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.switch_connection_to(conn_config)
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!='ActiveRecord::Base' && @dbcharmer_table_name_prefix.blank?
168
- @orig_table_name = table_name if defined?(:original_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 = "#{new_prefix}#{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(@orig_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
- @orig_table_name ? @orig_table_name : table_name
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.
@@ -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
@@ -3,7 +3,7 @@ module DbCharmer
3
3
  MAJOR = 1
4
4
  MINOR = 7
5
5
  PATCH = 4
6
- BUILD = 10
6
+ BUILD = 13
7
7
 
8
8
  STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join('.')
9
9
  end
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'
@@ -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 exists"
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
- connection = DbCharmer::Sharding.sharded_connection_by_connection_name(name)
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
- # itereate through entries in the shards_info table to find the
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
- structure_filename = "#{Rails.root}/db/#{origin_env}_#{name}_structure.sql"
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
- ENV['PGPORT'] = config["port"].to_s if config["port"]
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 db/#{origin_env}_#{name}_structure.sql #{search_path} #{config['database']}"
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
- structure_filename = "#{Rails.root}/db/#{origin_env}_#{name}_structure.sql"
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
- ENV['PGPORT'] = config["port"].to_s if config["port"]
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
- cmd = "psql -U #{config["username"]} -f #{structure_filename} #{config["database"]}"
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: 99
4
+ hash: 109
5
5
  prerelease:
6
6
  segments:
7
7
  - 1
8
8
  - 7
9
9
  - 4
10
- - 10
11
- version: 1.7.4.10
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-19 00:00:00 -07:00
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