switchman 1.5.21 → 2.1.5

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.
Files changed (56) hide show
  1. checksums.yaml +5 -5
  2. data/app/models/switchman/shard.rb +757 -11
  3. data/db/migrate/20130328212039_create_switchman_shards.rb +3 -1
  4. data/db/migrate/20130328224244_create_default_shard.rb +4 -2
  5. data/db/migrate/20161206323434_add_back_default_string_limits_switchman.rb +13 -0
  6. data/db/migrate/20180828183945_add_default_shard_index.rb +15 -0
  7. data/db/migrate/20180828192111_add_timestamps_to_shards.rb +17 -0
  8. data/db/migrate/20190114212900_add_unique_name_indexes.rb +9 -0
  9. data/lib/switchman/action_controller/caching.rb +2 -0
  10. data/lib/switchman/active_record/abstract_adapter.rb +14 -4
  11. data/lib/switchman/active_record/association.rb +64 -37
  12. data/lib/switchman/active_record/attribute_methods.rb +54 -22
  13. data/lib/switchman/active_record/base.rb +76 -31
  14. data/lib/switchman/active_record/batches.rb +3 -1
  15. data/lib/switchman/active_record/calculations.rb +17 -22
  16. data/lib/switchman/active_record/connection_handler.rb +88 -78
  17. data/lib/switchman/active_record/connection_pool.rb +28 -23
  18. data/lib/switchman/active_record/finder_methods.rb +37 -28
  19. data/lib/switchman/active_record/log_subscriber.rb +14 -19
  20. data/lib/switchman/active_record/migration.rb +80 -0
  21. data/lib/switchman/active_record/model_schema.rb +3 -1
  22. data/lib/switchman/active_record/persistence.rb +9 -1
  23. data/lib/switchman/active_record/postgresql_adapter.rb +170 -126
  24. data/lib/switchman/active_record/predicate_builder.rb +3 -1
  25. data/lib/switchman/active_record/query_cache.rb +22 -87
  26. data/lib/switchman/active_record/query_methods.rb +139 -125
  27. data/lib/switchman/active_record/reflection.rb +42 -14
  28. data/lib/switchman/active_record/relation.rb +108 -33
  29. data/lib/switchman/active_record/spawn_methods.rb +2 -0
  30. data/lib/switchman/active_record/statement_cache.rb +44 -52
  31. data/lib/switchman/active_record/table_definition.rb +4 -2
  32. data/lib/switchman/active_record/type_caster.rb +2 -0
  33. data/lib/switchman/active_record/where_clause_factory.rb +5 -2
  34. data/lib/switchman/active_support/cache.rb +18 -0
  35. data/lib/switchman/arel.rb +8 -25
  36. data/lib/switchman/call_super.rb +19 -0
  37. data/lib/switchman/connection_pool_proxy.rb +70 -24
  38. data/lib/switchman/database_server.rb +69 -59
  39. data/lib/switchman/default_shard.rb +3 -0
  40. data/lib/switchman/engine.rb +44 -41
  41. data/lib/switchman/environment.rb +2 -0
  42. data/lib/switchman/errors.rb +2 -0
  43. data/lib/switchman/{shackles → guard_rail}/relation.rb +7 -5
  44. data/lib/switchman/{shackles.rb → guard_rail.rb} +6 -4
  45. data/lib/switchman/open4.rb +2 -0
  46. data/lib/switchman/r_spec_helper.rb +14 -8
  47. data/lib/switchman/rails.rb +2 -0
  48. data/lib/switchman/schema_cache.rb +17 -0
  49. data/lib/switchman/sharded_instrumenter.rb +4 -2
  50. data/lib/switchman/standard_error.rb +4 -2
  51. data/lib/switchman/test_helper.rb +7 -10
  52. data/lib/switchman/version.rb +3 -1
  53. data/lib/switchman.rb +5 -1
  54. data/lib/tasks/switchman.rake +53 -72
  55. metadata +84 -38
  56. data/app/models/switchman/shard_internal.rb +0 -692
@@ -1,692 +0,0 @@
1
- require 'switchman/database_server'
2
- require 'switchman/default_shard'
3
- require 'switchman/environment'
4
- require 'switchman/errors'
5
-
6
- module Switchman
7
- class Shard < ::ActiveRecord::Base
8
- # ten trillion possible ids per shard. yup.
9
- IDS_PER_SHARD = 10_000_000_000_000
10
-
11
- CATEGORIES =
12
- {
13
- # special cased to mean all other models
14
- :default => nil,
15
- # special cased to not allow activating a shard other than the default
16
- :unsharded => [Shard]
17
- }
18
- private_constant :CATEGORIES
19
- @shard_category = :unsharded
20
-
21
- if defined?(::ProtectedAttributes)
22
- attr_accessible :default, :name, :database_server
23
- end
24
-
25
- # only allow one default
26
- validates_uniqueness_of :default, :if => lambda { |s| s.default? }
27
-
28
- after_save :clear_cache
29
-
30
- scope :primary, -> { where(name: nil).order(:database_server_id, :id).distinct_on(:database_server_id) }
31
-
32
- class << self
33
- def categories
34
- CATEGORIES.keys
35
- end
36
-
37
- def default(reload_deprecated = false, reload: false, with_fallback: false)
38
- reload = reload_deprecated if reload_deprecated
39
- if !@default || reload
40
- # Have to create a dummy object so that several key methods still work
41
- # (it's easier to do this in one place here, and just assume that sharding
42
- # is up and running everywhere else). This includes for looking up the
43
- # default shard itself. This also needs to be a local so that this method
44
- # can be re-entrant
45
- default = DefaultShard.instance
46
-
47
- # if we already have a default shard in place, and the caller wants
48
- # to use it as a fallback, use that instead of the dummy instance
49
- if with_fallback && @default
50
- default = @default
51
- end
52
-
53
- # the first time we need a dummy dummy for re-entrancy to avoid looping on ourselves
54
- @default ||= default
55
-
56
- # Now find the actual record, if it exists; rescue the fake default if the table doesn't exist
57
- @default = begin
58
- Shard.where(default: true).first || default
59
- rescue
60
- default
61
- end
62
-
63
- # rebuild current shard activations - it might have "another" default shard serialized there
64
- active_shards.replace(active_shards.map do |category, shard|
65
- shard = Shard.lookup((!shard || shard.default?) ? 'default' : shard.id)
66
- [category, shard]
67
- end.to_h)
68
-
69
- activate!(default: @default) if active_shards.empty?
70
- end
71
- @default
72
- end
73
-
74
- def current(category = :default)
75
- active_shards[category] || Shard.default
76
- end
77
-
78
- def activate(shards)
79
- old_shards = activate!(shards)
80
- yield
81
- ensure
82
- active_shards.merge!(old_shards) if old_shards
83
- end
84
-
85
- def activate!(shards)
86
- old_shards = nil
87
- currently_active_shards = active_shards
88
- shards.each do |category, shard|
89
- next if category == :unsharded
90
- unless currently_active_shards[category] == shard
91
- old_shards ||= {}
92
- old_shards[category] = currently_active_shards[category]
93
- currently_active_shards[category] = shard
94
- end
95
- end
96
- old_shards
97
- end
98
-
99
- def lookup(id)
100
- id_i = id.to_i
101
- return current if id_i == current.id || id == 'self'
102
- return default if id_i == default.id || id.nil? || id == 'default'
103
- id = id_i
104
- raise ArgumentError if id == 0
105
-
106
- unless cached_shards.has_key?(id)
107
- cached_shards[id] = Shard.default.activate do
108
- # can't simply cache the AR object since Shard has a custom serializer
109
- # that calls this method
110
- attributes = Switchman.cache.fetch(['shard', id].join('/')) do
111
- shard = find_by_id(id)
112
- if shard
113
- attributes = shard.attributes
114
- if ::Rails.version < '4.2'
115
- attributes.each_key do |key|
116
- attributes[key] = attributes[key].unserialize if attributes[key].is_a?(::ActiveRecord::AttributeMethods::Serialization::Attribute)
117
- end
118
- end
119
- attributes
120
- else
121
- :nil
122
- end
123
- end
124
- if attributes == :nil
125
- nil
126
- else
127
- shard = Shard.new
128
- attributes.each do |attr, value|
129
- shard.send(:"#{attr}=", value)
130
- end
131
- shard.instance_variable_set(:@new_record, false)
132
- # connection info doesn't exist in database.yml;
133
- # pretend the shard doesn't exist either
134
- shard = nil unless shard.database_server
135
- shard
136
- end
137
- end
138
- end
139
- cached_shards[id]
140
- end
141
-
142
- def clear_cache
143
- cached_shards.clear
144
- end
145
-
146
- # ==== Parameters
147
- #
148
- # * +shards+ - an array or relation of Shards to iterate over
149
- # * +categories+ - an array of categories to activate
150
- # * +options+ -
151
- # :parallel - true/false to execute in parallel, or a integer of how many
152
- # sub-processes per database server. Note that parallel
153
- # invocation currently uses forking, so should be used sparingly
154
- # because errors are not raised, and you cannot get results back
155
- # :max_procs - only run this many parallel processes at a time
156
- # :exception - :ignore, :raise, :defer (wait until the end and raise the first
157
- # error), or a proc
158
- def with_each_shard(*args)
159
- raise ArgumentError, "wrong number of arguments (#{args.length} for 0...3)" if args.length > 3
160
-
161
- unless default.is_a?(Shard)
162
- return Array.wrap(yield)
163
- end
164
-
165
- options = args.extract_options!
166
- if args.length == 1
167
- if Array === args.first && args.first.first.is_a?(Symbol)
168
- categories = args.first
169
- else
170
- scope = args.first
171
- end
172
- else
173
- scope, categories = args
174
- end
175
-
176
- parallel = case options[:parallel]
177
- when true
178
- 1
179
- when false, nil
180
- 0
181
- else
182
- options[:parallel]
183
- end
184
- options.delete(:parallel)
185
-
186
- scope ||= Shard.all
187
- if ::ActiveRecord::Relation === scope && scope.order_values.empty?
188
- scope = scope.order("database_server_id IS NOT NULL, database_server_id, id")
189
- end
190
-
191
- if parallel > 0
192
- max_procs = determine_max_procs(options.delete(:max_procs), parallel)
193
- if ::ActiveRecord::Relation === scope
194
- # still need a post-uniq, cause the default database server could be NULL or Rails.env in the db
195
- database_servers = scope.reorder('database_server_id').select(:database_server_id).uniq.
196
- map(&:database_server).compact.uniq
197
- parallel = [(max_procs.to_f / database_servers.count).ceil, parallel].min if max_procs
198
-
199
- scopes = Hash[database_servers.map do |server|
200
- server_scope = server.shards.merge(scope)
201
- if parallel == 1
202
- subscopes = [server_scope]
203
- else
204
- subscopes = []
205
- total = server_scope.count
206
- ranges = []
207
- server_scope.find_ids_in_ranges(:batch_size => (total.to_f / parallel).ceil) do |min, max|
208
- ranges << [min, max]
209
- end
210
- # create a half-open range on the last one
211
- ranges.last[1] = nil
212
- ranges.each do |min, max|
213
- subscope = server_scope.where("id>=?", min)
214
- subscope = subscope.where("id<=?", max) if max
215
- subscopes << subscope
216
- end
217
- end
218
- [server, subscopes]
219
- end]
220
- else
221
- scopes = scope.group_by(&:database_server)
222
- if parallel > 1
223
- parallel = [(max_procs.to_f / scopes.count).ceil, parallel].min if max_procs
224
- scopes = Hash[scopes.map do |(server, shards)|
225
- [server, shards.in_groups(parallel, false).compact]
226
- end]
227
- end
228
- end
229
-
230
- exception_pipes = []
231
- pids = []
232
- out_fds = []
233
- err_fds = []
234
- pid_to_name_map = {}
235
- fd_to_name_map = {}
236
- errors = []
237
-
238
- wait_for_output = lambda do |out_fds, err_fds, fd_to_name_map|
239
- ready, _ = IO.select(out_fds + err_fds)
240
- ready.each do |fd|
241
- if fd.eof?
242
- fd.close
243
- out_fds.delete(fd)
244
- err_fds.delete(fd)
245
- next
246
- end
247
- line = fd.readline
248
- puts "#{fd_to_name_map[fd]}: #{line}"
249
- end
250
- end
251
-
252
- # only one process; don't bother forking
253
- if scopes.length == 1 && parallel == 1
254
- return with_each_shard(subscopes.first, categories, options) { yield }
255
- end
256
-
257
- # clear connections prior to forking (no more queries will be executed in the parent,
258
- # and we want them gone so that we don't accidentally use them post-fork doing something
259
- # silly like dealloc'ing prepared statements)
260
- ::ActiveRecord::Base.clear_all_connections!
261
-
262
- scopes.each do |server, subscopes|
263
- if !(::ActiveRecord::Relation === subscopes.first) && subscopes.first.class != Array
264
- subscopes = [subscopes]
265
- end
266
-
267
- subscopes.each_with_index do |subscope, idx|
268
- if subscopes.length > 1
269
- name = "#{server.id} #{idx + 1}"
270
- else
271
- name = server.id
272
- end
273
-
274
- exception_pipe = IO.pipe
275
- exception_pipes << exception_pipe
276
- pid, io_in, io_out, io_err = Open4.pfork4(lambda do
277
- begin
278
- Switchman.config[:on_fork_proc].try(:call)
279
- $0 = [$0, ARGV, name].flatten.join(' ')
280
- with_each_shard(subscope, categories, options) { yield }
281
- exception_pipe.last.close
282
- rescue => e
283
- begin
284
- dumped = Marshal.dump(e)
285
- rescue
286
- # couldn't dump the exception; create a copy with just
287
- # the message and the backtrace
288
- e2 = e.class.new(e.message)
289
- e2.set_backtrace(e.backtrace)
290
- e2.instance_variable_set(:@active_shards, e.instance_variable_get(:@active_shards))
291
- dumped = Marshal.dump(e2)
292
- end
293
- exception_pipe.last.set_encoding(dumped.encoding)
294
- exception_pipe.last.write(dumped)
295
- exception_pipe.last.flush
296
- exception_pipe.last.close
297
- exit! 1
298
- end
299
- end)
300
- exception_pipe.last.close
301
- pids << pid
302
- io_in.close # don't care about writing to stdin
303
- out_fds << io_out
304
- err_fds << io_err
305
- pid_to_name_map[pid] = name
306
- fd_to_name_map[io_out] = name
307
- fd_to_name_map[io_err] = name
308
-
309
- while max_procs && pids.count >= max_procs
310
- while max_procs && out_fds.count >= max_procs
311
- # wait for output if we've hit the max_procs limit
312
- wait_for_output.call(out_fds, err_fds, fd_to_name_map)
313
- end
314
- # we've gotten all the output from one fd so wait for its child process to exit
315
- found_pid, status = Process.wait2
316
- pids.delete(found_pid)
317
- errors << pid_to_name_map[found_pid] if status.exitstatus != 0
318
- end
319
- end
320
- end
321
-
322
- while out_fds.any? || err_fds.any?
323
- wait_for_output.call(out_fds, err_fds, fd_to_name_map)
324
- end
325
- pids.each do |pid|
326
- _, status = Process.waitpid2(pid)
327
- errors << pid_to_name_map[pid] if status.exitstatus != 0
328
- end
329
-
330
- # check for an exception; we only re-raise the first one
331
- exception_pipes.each do |exception_pipe|
332
- begin
333
- serialized_exception = exception_pipe.first.read
334
- next if serialized_exception.empty?
335
- exception = Marshal.load(serialized_exception)
336
- raise exception
337
- ensure
338
- exception_pipe.first.close
339
- end
340
- end
341
-
342
- unless errors.empty?
343
- raise ParallelShardExecError.new("The following subprocesses did not exit cleanly: #{errors.sort.join(", ")}")
344
- end
345
- return
346
- end
347
-
348
- categories ||= []
349
-
350
- previous_shard = nil
351
- close_connections_if_needed = lambda do |shard|
352
- # prune the prior connection unless it happened to be the same
353
- if previous_shard && shard != previous_shard &&
354
- (shard.database_server != previous_shard.database_server || !previous_shard.database_server.shareable?)
355
- previous_shard.activate do
356
- ::Shackles.activated_environments.each do |env|
357
- ::Shackles.activate(env) do
358
- if ::ActiveRecord::Base.connected? && ::ActiveRecord::Base.connection.open_transactions == 0
359
- ::ActiveRecord::Base.connection_pool.current_pool.disconnect!
360
- end
361
- end
362
- end
363
- end
364
- end
365
- end
366
-
367
- result = []
368
- exception = nil
369
- scope.each do |shard|
370
- # shard references a database server that isn't configured in this environment
371
- next unless shard.database_server
372
- close_connections_if_needed.call(shard)
373
- shard.activate(*categories) do
374
- begin
375
- result.concat Array.wrap(yield)
376
- rescue
377
- case options[:exception]
378
- when :ignore
379
- when :defer
380
- exception ||= $!
381
- when Proc
382
- options[:exception].call
383
- when :raise
384
- raise
385
- else
386
- raise
387
- end
388
- end
389
- end
390
- previous_shard = shard
391
- end
392
- close_connections_if_needed.call(Shard.current)
393
- raise exception if exception
394
- result
395
- end
396
-
397
- def partition_by_shard(array, partition_proc = nil)
398
- shard_arrays = {}
399
- array.each do |object|
400
- partition_object = partition_proc ? partition_proc.call(object) : object
401
- case partition_object
402
- when Shard
403
- shard = partition_object
404
- when ::ActiveRecord::Base
405
- if partition_object.respond_to?(:associated_shards)
406
- partition_object.associated_shards.each do |a_shard|
407
- shard_arrays[a_shard] ||= []
408
- shard_arrays[a_shard] << object
409
- end
410
- next
411
- else
412
- shard = partition_object.shard
413
- end
414
- when Integer, /^\d+$/, /^(\d+)~(\d+)$/
415
- local_id, shard = Shard.local_id_for(partition_object)
416
- local_id ||= partition_object
417
- object = local_id if !partition_proc
418
- end
419
- shard ||= Shard.current
420
- shard_arrays[shard] ||= []
421
- shard_arrays[shard] << object
422
- end
423
- # TODO: use with_each_shard (or vice versa) to get
424
- # connection management and parallelism benefits
425
- shard_arrays.inject([]) do |results, (shard, objects)|
426
- results.concat shard.activate { Array.wrap(yield objects) }
427
- end
428
- end
429
-
430
- # converts an AR object, integral id, string id, or string short-global-id to a
431
- # integral id. nil if it can't be interpreted
432
- def integral_id_for(any_id)
433
- if ::Rails.version >= '4.2' && any_id.is_a?(::Arel::Nodes::Casted)
434
- any_id = any_id.val
435
- end
436
-
437
- case any_id
438
- when ::ActiveRecord::Base
439
- any_id.id
440
- when /^(\d+)~(\d+)$/
441
- local_id = $2.to_i
442
- # doesn't make sense to have a double-global id
443
- return nil if local_id > IDS_PER_SHARD
444
- $1.to_i * IDS_PER_SHARD + local_id
445
- when Integer, /^\d+$/
446
- any_id.to_i
447
- else
448
- nil
449
- end
450
- end
451
-
452
- # takes an id-ish, and returns a local id and the shard it's
453
- # local to. [nil, nil] if it can't be interpreted. [id, nil]
454
- # if it's already a local ID. [nil, nil] if it's a well formed
455
- # id, but the shard it refers to does not exist
456
- NIL_NIL_ID = [nil, nil].freeze
457
- def local_id_for(any_id)
458
- id = integral_id_for(any_id)
459
- return NIL_NIL_ID unless id
460
- if id < IDS_PER_SHARD
461
- [id, nil]
462
- elsif shard = lookup(id / IDS_PER_SHARD)
463
- [id % IDS_PER_SHARD, shard]
464
- else
465
- NIL_NIL_ID
466
- end
467
- end
468
-
469
- # takes an id-ish, and returns an integral id relative to
470
- # target_shard. returns nil if it can't be interpreted,
471
- # or the integral version of the id if it refers to a shard
472
- # that does not exist
473
- def relative_id_for(any_id, source_shard, target_shard)
474
- integral_id = integral_id_for(any_id)
475
- local_id, shard = local_id_for(integral_id)
476
- return integral_id unless local_id
477
- shard ||= source_shard
478
- return local_id if shard == target_shard
479
- shard.global_id_for(local_id)
480
- end
481
-
482
- # takes an id-ish, and returns a shortened global
483
- # string id if global, and itself if local.
484
- # returns any_id itself if it can't be interpreted
485
- def short_id_for(any_id)
486
- local_id, shard = local_id_for(any_id)
487
- return any_id unless local_id
488
- return local_id unless shard
489
- "#{shard.id}~#{local_id}"
490
- end
491
-
492
- # takes an id-ish, and returns an integral global id.
493
- # returns nil if it can't be interpreted
494
- def global_id_for(any_id, source_shard = nil)
495
- id = integral_id_for(any_id)
496
- return any_id unless id
497
- if id >= IDS_PER_SHARD
498
- id
499
- else
500
- source_shard ||= Shard.current
501
- source_shard.global_id_for(id)
502
- end
503
- end
504
-
505
- def shard_for(any_id, source_shard = nil)
506
- return any_id.shard if any_id.is_a?(::ActiveRecord::Base)
507
- _, shard = local_id_for(any_id)
508
- shard || source_shard || Shard.current
509
- end
510
-
511
- # given the provided option, determines whether we need to (and whether
512
- # it's possible) to determine a reasonable default.
513
- def determine_max_procs(max_procs_input, parallel_input=2)
514
- max_procs = nil
515
- if max_procs_input
516
- max_procs = max_procs_input.to_i
517
- max_procs = nil if max_procs == 0
518
- else
519
- return 1 if parallel_input.nil? || parallel_input < 1
520
- cpus = Environment.cpu_count
521
- if cpus && cpus > 0
522
- max_procs = cpus * parallel_input
523
- end
524
- end
525
-
526
- return max_procs
527
- end
528
-
529
- private
530
- # in-process caching
531
- def cached_shards
532
- @cached_shards ||= {}.compare_by_identity
533
- end
534
-
535
- def add_to_cache(shard)
536
- cached_shards[shard.id] = shard
537
- end
538
-
539
- def remove_from_cache(shard)
540
- cached_shards.delete(shard.id)
541
- end
542
-
543
- def active_shards
544
- Thread.current[:active_shards] ||= {}.compare_by_identity
545
- end
546
- end
547
-
548
- def name
549
- unless instance_variable_defined?(:@name)
550
- # protect against re-entrancy
551
- @name = nil
552
- @name = read_attribute(:name) || default_name
553
- end
554
- @name
555
- end
556
-
557
- def name=(name)
558
- write_attribute(:name, @name = name)
559
- remove_instance_variable(:@name) if name == nil
560
- end
561
-
562
- def database_server
563
- @database_server ||= DatabaseServer.find(self.database_server_id)
564
- end
565
-
566
- def database_server=(database_server)
567
- self.database_server_id = database_server.id
568
- @database_server = database_server
569
- end
570
-
571
- def primary?
572
- self == database_server.primary_shard
573
- end
574
-
575
- def description
576
- [database_server.id, name].compact.join(':')
577
- end
578
-
579
- # Shards are always on the default shard
580
- def shard
581
- Shard.default
582
- end
583
-
584
- def activate(*categories)
585
- shards = hashify_categories(categories)
586
- Shard.activate(shards) do
587
- yield
588
- end
589
- end
590
-
591
- # for use from console ONLY
592
- def activate!(*categories)
593
- shards = hashify_categories(categories)
594
- Shard.activate!(shards)
595
- nil
596
- end
597
-
598
- # custom serialization, since shard is self-referential
599
- def _dump(depth)
600
- self.id.to_s
601
- end
602
-
603
- def self._load(str)
604
- lookup(str.to_i)
605
- end
606
-
607
- def drop_database
608
- raise("Cannot drop the database of the default shard") if self.default?
609
- return unless read_attribute(:name)
610
-
611
- begin
612
- adapter = self.database_server.config[:adapter]
613
- sharding_config = Switchman.config || {}
614
- drop_statement = sharding_config[adapter].try(:[], :drop_statement)
615
- drop_statement ||= sharding_config[:drop_statement]
616
- if drop_statement
617
- drop_statement = Array(drop_statement).dup.
618
- map { |statement| statement.gsub('%{name}', self.name) }
619
- end
620
-
621
- case adapter
622
- when 'mysql', 'mysql2'
623
- self.activate do
624
- ::Shackles.activate(:deploy) do
625
- drop_statement ||= "DROP DATABASE #{self.name}"
626
- Array(drop_statement).each do |stmt|
627
- ::ActiveRecord::Base.connection.execute(stmt)
628
- end
629
- end
630
- end
631
- when 'postgresql'
632
- self.activate do
633
- ::Shackles.activate(:deploy) do
634
- # Shut up, Postgres!
635
- conn = ::ActiveRecord::Base.connection
636
- old_proc = conn.raw_connection.set_notice_processor {}
637
- begin
638
- drop_statement ||= "DROP SCHEMA #{self.name} CASCADE"
639
- Array(drop_statement).each do |stmt|
640
- ::ActiveRecord::Base.connection.execute(stmt)
641
- end
642
- ensure
643
- conn.raw_connection.set_notice_processor(&old_proc) if old_proc
644
- end
645
- end
646
- end
647
- when 'sqlite3'
648
- File.delete(self.name) unless self.name == ':memory:'
649
- end
650
- rescue
651
- logger.info "Drop failed: #{$!}"
652
- end
653
- end
654
-
655
- # takes an id local to this shard, and returns a global id
656
- def global_id_for(local_id)
657
- return nil unless local_id
658
- local_id + self.id * IDS_PER_SHARD
659
- end
660
-
661
- # skip global_id.hash
662
- def hash
663
- id.hash
664
- end
665
-
666
- def destroy
667
- raise("Cannot destroy the default shard") if self.default?
668
- super
669
- end
670
-
671
- private
672
-
673
- def clear_cache
674
- Shard.default.activate do
675
- Switchman.cache.delete(['shard', id].join('/'))
676
- end
677
- end
678
-
679
- def default_name
680
- database_server.shard_name(self)
681
- end
682
-
683
- def hashify_categories(categories)
684
- if categories.empty?
685
- { :default => self }
686
- else
687
- categories.inject({}) { |h, category| h[category] = self; h }
688
- end
689
- end
690
-
691
- end
692
- end