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.
@@ -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