switchman 1.13.3 → 2.2.0

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 +4 -4
  2. data/app/models/switchman/shard.rb +712 -11
  3. data/db/migrate/20130328212039_create_switchman_shards.rb +2 -0
  4. data/db/migrate/20130328224244_create_default_shard.rb +3 -1
  5. data/db/migrate/20161206323434_add_back_default_string_limits_switchman.rb +2 -0
  6. data/db/migrate/20180828183945_add_default_shard_index.rb +3 -1
  7. data/db/migrate/20180828192111_add_timestamps_to_shards.rb +2 -0
  8. data/db/migrate/20190114212900_add_unique_name_indexes.rb +2 -0
  9. data/lib/switchman/action_controller/caching.rb +2 -0
  10. data/lib/switchman/active_record/abstract_adapter.rb +6 -4
  11. data/lib/switchman/active_record/association.rb +45 -16
  12. data/lib/switchman/active_record/attribute_methods.rb +43 -17
  13. data/lib/switchman/active_record/base.rb +60 -20
  14. data/lib/switchman/active_record/batches.rb +2 -0
  15. data/lib/switchman/active_record/calculations.rb +5 -2
  16. data/lib/switchman/active_record/connection_handler.rb +48 -25
  17. data/lib/switchman/active_record/connection_pool.rb +19 -15
  18. data/lib/switchman/active_record/finder_methods.rb +2 -0
  19. data/lib/switchman/active_record/log_subscriber.rb +10 -12
  20. data/lib/switchman/active_record/migration.rb +48 -2
  21. data/lib/switchman/active_record/model_schema.rb +3 -1
  22. data/lib/switchman/active_record/persistence.rb +13 -2
  23. data/lib/switchman/active_record/postgresql_adapter.rb +149 -139
  24. data/lib/switchman/active_record/predicate_builder.rb +3 -1
  25. data/lib/switchman/active_record/query_cache.rb +19 -107
  26. data/lib/switchman/active_record/query_methods.rb +25 -3
  27. data/lib/switchman/active_record/reflection.rb +21 -8
  28. data/lib/switchman/active_record/relation.rb +66 -10
  29. data/lib/switchman/active_record/spawn_methods.rb +2 -0
  30. data/lib/switchman/active_record/statement_cache.rb +6 -25
  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 +2 -0
  34. data/lib/switchman/active_support/cache.rb +18 -0
  35. data/lib/switchman/arel.rb +2 -0
  36. data/lib/switchman/call_super.rb +2 -0
  37. data/lib/switchman/connection_pool_proxy.rb +44 -22
  38. data/lib/switchman/database_server.rb +34 -19
  39. data/lib/switchman/default_shard.rb +3 -0
  40. data/lib/switchman/engine.rb +20 -15
  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 +9 -7
  47. data/lib/switchman/rails.rb +2 -0
  48. data/lib/switchman/schema_cache.rb +11 -1
  49. data/lib/switchman/sharded_instrumenter.rb +3 -1
  50. data/lib/switchman/standard_error.rb +3 -1
  51. data/lib/switchman/test_helper.rb +7 -11
  52. data/lib/switchman/version.rb +3 -1
  53. data/lib/switchman.rb +5 -1
  54. data/lib/tasks/switchman.rake +24 -17
  55. metadata +53 -26
  56. data/app/models/switchman/shard_internal.rb +0 -714
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Switchman
2
4
  module ActiveRecord
3
5
  module QueryMethods
@@ -227,6 +229,14 @@ module Switchman
227
229
  connection.with_local_table_name { super }
228
230
  end
229
231
 
232
+ def arel_column(columns)
233
+ connection.with_local_table_name { super }
234
+ end
235
+
236
+ def table_name_matches?(from)
237
+ connection.with_global_table_name { super }
238
+ end
239
+
230
240
  # semi-private
231
241
  public
232
242
  def transpose_predicates(predicates,
@@ -236,6 +246,18 @@ module Switchman
236
246
  binds: nil,
237
247
  dup_binds_on_mutation: false)
238
248
  result = predicates.map do |predicate|
249
+ if ::Rails.version >= '5.2' && predicate.is_a?(::Arel::Nodes::And)
250
+ new_predicates, _binds = transpose_predicates(predicate.children, source_shard, target_shard,
251
+ remove_nonlocal_primary_keys,
252
+ binds: binds,
253
+ dup_binds_on_mutation: dup_binds_on_mutation)
254
+ next (if new_predicates == predicate.children
255
+ predicate
256
+ else
257
+ ::Arel::Nodes::And.new(new_predicates)
258
+ end)
259
+ end
260
+
239
261
  next predicate unless predicate.is_a?(::Arel::Nodes::Binary)
240
262
  next predicate unless predicate.left.is_a?(::Arel::Attributes::Attribute)
241
263
  relation, column = relation_and_column(predicate.left)
@@ -244,7 +266,7 @@ module Switchman
244
266
  remove = true if type == :primary &&
245
267
  remove_nonlocal_primary_keys &&
246
268
  predicate.left.relation.model == klass &&
247
- predicate.is_a?(::Arel::Nodes::Equality)
269
+ (predicate.is_a?(::Arel::Nodes::Equality) || predicate.is_a?(::Arel::Nodes::In))
248
270
 
249
271
  current_source_shard =
250
272
  if source_shard
@@ -259,7 +281,7 @@ module Switchman
259
281
  new_right_value =
260
282
  case predicate.right
261
283
  when Array
262
- predicate.right.map {|val| transpose_predicate_value(val, current_source_shard, target_shard, type, remove) }
284
+ predicate.right.map {|val| transpose_predicate_value(val, current_source_shard, target_shard, type, remove).presence }.compact
263
285
  else
264
286
  transpose_predicate_value(predicate.right, current_source_shard, target_shard, type, remove)
265
287
  end
@@ -334,7 +356,7 @@ module Switchman
334
356
  value
335
357
  else
336
358
  local_id = Shard.relative_id_for(current_id, current_shard, target_shard) || current_id
337
- local_id = [] if remove_non_local_ids && local_id.is_a?(Integer) && local_id > Shard::IDS_PER_SHARD
359
+ return nil if remove_non_local_ids && local_id.is_a?(Integer) && local_id > Shard::IDS_PER_SHARD
338
360
  if current_id != local_id
339
361
  # make a new bind param
340
362
  ::Arel::Nodes::BindParam.new(query_att.class.new(query_att.name, local_id, query_att.type))
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Switchman
2
4
  module ActiveRecord
3
5
  module Reflection
@@ -26,15 +28,26 @@ module Switchman
26
28
  # this technically belongs on AssociationReflection, but we put it on
27
29
  # ThroughReflection as well, instead of delegating to its internal
28
30
  # HasManyAssociation, losing its proper `klass`
29
- def association_scope_cache(conn, owner, &block)
30
- key = conn.prepared_statements
31
- if polymorphic?
32
- key = [key, owner._read_attribute(@foreign_type)]
31
+ if ::Rails.version < '6.0.4'
32
+ def association_scope_cache(conn, owner, &block)
33
+ key = conn.prepared_statements
34
+ if polymorphic?
35
+ key = [key, owner._read_attribute(@foreign_type)]
36
+ end
37
+ key = [key, shard(owner).id].flatten
38
+ @association_scope_cache[key] ||= @scope_lock.synchronize {
39
+ @association_scope_cache[key] ||= (::Rails.version >= "5.2" ? ::ActiveRecord::StatementCache.create(conn, &block) : block.call)
40
+ }
41
+ end
42
+ else
43
+ def association_scope_cache(klass, owner, &block)
44
+ key = self
45
+ if polymorphic?
46
+ key = [key, owner._read_attribute(@foreign_type)]
47
+ end
48
+ key = [key, shard(owner).id].flatten
49
+ klass.cached_find_by_statement(key, &block)
33
50
  end
34
- key = [key, shard(owner).id].flatten
35
- @association_scope_cache[key] ||= @scope_lock.synchronize {
36
- @association_scope_cache[key] ||= (::Rails.version >= "5.2" ? ::ActiveRecord::StatementCache.create(conn, &block) : block.call)
37
- }
38
51
  end
39
52
  end
40
53
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Switchman
2
4
  module ActiveRecord
3
5
  module Relation
@@ -5,7 +7,7 @@ module Switchman
5
7
  klass::SINGLE_VALUE_METHODS.concat [ :shard, :shard_source ]
6
8
  end
7
9
 
8
- def initialize(*args)
10
+ def initialize(*, **)
9
11
  super
10
12
  self.shard_value = Shard.current(klass ? klass.shard_category : :primary) unless shard_value
11
13
  self.shard_source_value = :implicit unless shard_source_value
@@ -17,7 +19,7 @@ module Switchman
17
19
  result
18
20
  end
19
21
 
20
- def merge(*args)
22
+ def merge(*)
21
23
  relation = super
22
24
  if relation.shard_value != self.shard_value && relation.shard_source_value == :implicit
23
25
  relation.shard_value = self.shard_value
@@ -26,15 +28,15 @@ module Switchman
26
28
  relation
27
29
  end
28
30
 
29
- def new(*args, &block)
31
+ def new(*, &block)
30
32
  primary_shard.activate(klass.shard_category) { super }
31
33
  end
32
34
 
33
- def create(*args, &block)
35
+ def create(*, &block)
34
36
  primary_shard.activate(klass.shard_category) { super }
35
37
  end
36
38
 
37
- def create!(*args, &block)
39
+ def create!(*, &block)
38
40
  primary_shard.activate(klass.shard_category) { super }
39
41
  end
40
42
 
@@ -60,7 +62,7 @@ module Switchman
60
62
  %I{update_all delete_all}.each do |method|
61
63
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
62
64
  def #{method}(*args)
63
- result = self.activate { |relation| relation.call_super(#{method.inspect}, Relation, *args) }
65
+ result = self.activate(unordered: true) { |relation| relation.call_super(#{method.inspect}, Relation, *args) }
64
66
  result = result.sum if result.is_a?(Array)
65
67
  result
66
68
  end
@@ -95,7 +97,7 @@ module Switchman
95
97
  end
96
98
  end
97
99
 
98
- def activate(&block)
100
+ def activate(unordered: false, &block)
99
101
  shards = all_shards
100
102
  if (Array === shards && shards.length == 1)
101
103
  if shards.first == DefaultShard || shards.first == Shard.current(klass.shard_category)
@@ -104,10 +106,64 @@ module Switchman
104
106
  shards.first.activate(klass.shard_category) { yield(self, shards.first) }
105
107
  end
106
108
  else
107
- # TODO: implement local limit to avoid querying extra shards
108
- Shard.with_each_shard(shards, [klass.shard_category]) do
109
- shard(Shard.current(klass.shard_category), :to_a).activate(&block)
109
+ result_count = 0
110
+ can_order = false
111
+ result = Shard.with_each_shard(shards, [klass.shard_category]) do
112
+ # don't even query other shards if we're already past the limit
113
+ next if limit_value && result_count >= limit_value && order_values.empty?
114
+
115
+ relation = shard(Shard.current(klass.shard_category), :to_a)
116
+ # do a minimal query if possible
117
+ relation = relation.limit(limit_value - result_count) if limit_value && !result_count.zero? && order_values.empty?
118
+
119
+ shard_results = relation.activate(&block)
120
+
121
+ if shard_results.present? && !unordered
122
+ can_order ||= can_order_cross_shard_results? unless order_values.empty?
123
+ raise OrderOnMultiShardQuery if !can_order && !order_values.empty? && result_count.positive?
124
+
125
+ result_count += shard_results.is_a?(Array) ? shard_results.length : 1
126
+ end
127
+ shard_results
128
+ end
129
+
130
+ result = reorder_cross_shard_results(result) if can_order
131
+ result.slice!(limit_value..-1) if limit_value
132
+ result
133
+ end
134
+ end
135
+
136
+ def can_order_cross_shard_results?
137
+ # we only presume to be able to post-sort the most basic of orderings
138
+ order_values.all? { |ov| ov.is_a?(::Arel::Nodes::Ordering) && ov.expr.is_a?(::Arel::Attributes::Attribute) }
139
+ end
140
+
141
+ def reorder_cross_shard_results(results)
142
+ results.sort! do |l, r|
143
+ result = 0
144
+ order_values.each do |ov|
145
+ if !l.is_a?(::ActiveRecord::Base)
146
+ a, b = l, r
147
+ elsif l.respond_to?(ov.expr.name)
148
+ a = l.send(ov.expr.name)
149
+ b = r.send(ov.expr.name)
150
+ else
151
+ a = l.attributes[ov.expr.name]
152
+ b = r.attributes[ov.expr.name]
153
+ end
154
+ next if a == b
155
+
156
+ if a.nil? || b.nil?
157
+ result = 1 if a.nil?
158
+ result *= -1 if ov.is_a?(::Arel::Nodes::Descending)
159
+ else
160
+ result = a <=> b
161
+ end
162
+
163
+ result *= -1 if ov.is_a?(::Arel::Nodes::Descending)
164
+ break unless result.zero?
110
165
  end
166
+ result
111
167
  end
112
168
  end
113
169
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Switchman
2
4
  module ActiveRecord
3
5
  module SpawnMethods
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Switchman
2
4
  module ActiveRecord
3
5
  module StatementCache
4
6
  module ClassMethods
5
- def create(connection, block = Proc.new)
7
+ def create(connection, &block)
6
8
  relation = block.call ::ActiveRecord::StatementCache::Params.new
7
9
 
8
10
  if ::Rails.version >= "5.2"
@@ -46,37 +48,16 @@ module Switchman
46
48
  bind_values = bind_map.bind(params, current_shard, target_shard)
47
49
 
48
50
  target_shard.activate(klass.shard_category) do
49
- if connection.use_qualified_names?
50
- sql = qualified_query_builder(target_shard, klass).sql_for(bind_values, connection)
51
- klass.find_by_sql(sql, bind_values)
52
- else
53
- sql = generic_query_builder(connection).sql_for(bind_values, connection)
54
- klass.find_by_sql(sql, bind_values)
55
- end
51
+ sql = qualified_query_builder(target_shard, klass).sql_for(bind_values, connection)
52
+ klass.find_by_sql(sql, bind_values)
56
53
  end
57
54
  end
58
55
 
59
- if ::Rails.version < '5.1'
60
- def generic_query_builder(connection)
61
- @query_builder ||= connection.cacheable_query(@arel)
62
- end
63
-
64
- def qualified_query_builder(shard, klass)
65
- @qualified_query_builders[shard.id] ||= klass.connection.cacheable_query(@arel)
66
- end
67
- elsif ::Rails.version < '5.2'
68
- def generic_query_builder(connection)
69
- @query_builder ||= connection.cacheable_query(self.class, @arel)
70
- end
71
-
56
+ if ::Rails.version < '5.2'
72
57
  def qualified_query_builder(shard, klass)
73
58
  @qualified_query_builders[shard.id] ||= klass.connection.cacheable_query(self.class, @arel)
74
59
  end
75
60
  else
76
- def generic_query_builder(connection)
77
- @query_builder ||= connection.cacheable_query(self.class, @arel).first
78
- end
79
-
80
61
  def qualified_query_builder(shard, klass)
81
62
  @qualified_query_builders[shard.id] ||= klass.connection.cacheable_query(self.class, @arel).first
82
63
  end
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Switchman
2
4
  module ActiveRecord
3
5
  module TableDefinition
4
- def column(name, type, options = {})
5
- Engine.foreign_key_check(name, type, options)
6
+ def column(name, type, limit: nil, **)
7
+ Engine.foreign_key_check(name, type, limit: limit)
6
8
  super
7
9
  end
8
10
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Switchman
2
4
  module ActiveRecord
3
5
  module TypeCaster
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Switchman
2
4
  module ActiveRecord
3
5
  module WhereClauseFactory
@@ -1,13 +1,31 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Switchman
2
4
  module ActiveSupport
3
5
  module Cache
4
6
  module ClassMethods
5
7
  def lookup_store(*store_options)
6
8
  store = super
9
+ # can't use defined?, because it's a _ruby_ autoloaded constant,
10
+ # so just checking that will cause it to get required
11
+ if store.class.name == "ActiveSupport::Cache::RedisCacheStore" && !::ActiveSupport::Cache::RedisCacheStore.ancestors.include?(RedisCacheStore)
12
+ ::ActiveSupport::Cache::RedisCacheStore.prepend(RedisCacheStore)
13
+ end
7
14
  store.options[:namespace] ||= lambda { Shard.current.default? ? nil : "shard_#{Shard.current.id}" }
8
15
  store
9
16
  end
10
17
  end
18
+
19
+ module RedisCacheStore
20
+ def clear(namespace: nil, **)
21
+ # RedisCacheStore tries to be smart and only clear the cache under your namespace, if you have one set
22
+ # unfortunately, it uses the keys command, which is extraordinarily inefficient in a large redis instance
23
+ # fortunately, we can assume we control the entire instance, because we set up the namespacing, so just
24
+ # always unset it temporarily for clear calls
25
+ namespace = nil
26
+ super
27
+ end
28
+ end
11
29
  end
12
30
  end
13
31
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Switchman
2
4
  module Arel
3
5
  module Table
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Switchman
2
4
  module CallSuper
3
5
  def super_method_above(method_name, above_module)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'switchman/schema_cache'
2
4
 
3
5
  module Switchman
@@ -23,20 +25,27 @@ module Switchman
23
25
  @category = category
24
26
  @default_pool = default_pool
25
27
  @connection_pools = shard_connection_pools
26
- @schema_cache = SchemaCache.new(self)
28
+ @schema_cache = default_pool.get_schema_cache(nil) if ::Rails.version >= '6'
29
+ @schema_cache = SchemaCache.new(self) unless @schema_cache.is_a?(SchemaCache)
30
+ if ::Rails.version >= '6'
31
+ @default_pool.set_schema_cache(@schema_cache)
32
+ @connection_pools.each_value do |pool|
33
+ pool.set_schema_cache(@schema_cache)
34
+ end
35
+ end
27
36
  end
28
37
 
29
38
  def active_shard
30
39
  Shard.current(@category)
31
40
  end
32
41
 
33
- def active_shackles_environment
34
- ::Rails.env.test? ? :master : active_shard.database_server.shackles_environment
42
+ def active_guard_rail_environment
43
+ ::Rails.env.test? ? :primary : active_shard.database_server.guard_rail_environment
35
44
  end
36
45
 
37
46
  def current_pool
38
47
  current_active_shard = active_shard
39
- pool = self.default_pool if current_active_shard.database_server == Shard.default.database_server && active_shackles_environment == :master && (current_active_shard.default? || current_active_shard.database_server.shareable?)
48
+ pool = self.default_pool if current_active_shard.database_server == Shard.default.database_server && active_guard_rail_environment == :primary && (current_active_shard.default? || current_active_shard.database_server.shareable?)
40
49
  pool = @connection_pools[pool_key] ||= create_pool unless pool
41
50
  pool.shard = current_active_shard
42
51
  pool
@@ -46,21 +55,21 @@ module Switchman
46
55
  connection_pools.map(&:connections).inject([], &:+)
47
56
  end
48
57
 
49
- def connection
58
+ def connection(switch_shard: true)
50
59
  pool = current_pool
51
60
  begin
52
- connection = pool.connection
53
- connection.instance_variable_set(:@schema_cache, @schema_cache)
61
+ connection = pool.connection(switch_shard: switch_shard)
62
+ connection.instance_variable_set(:@schema_cache, @schema_cache) unless ::Rails.version >= '6'
54
63
  connection
55
64
  rescue ConnectionError
56
- raise if active_shard.database_server == Shard.default.database_server && active_shackles_environment == :master
57
- configs = active_shard.database_server.config(active_shackles_environment)
65
+ raise if active_shard.database_server == Shard.default.database_server && active_guard_rail_environment == :primary
66
+ configs = active_shard.database_server.config(active_guard_rail_environment)
58
67
  raise unless configs.is_a?(Array)
59
68
  configs.each_with_index do |config, idx|
60
69
  pool = create_pool(config.dup)
61
70
  begin
62
71
  connection = pool.connection
63
- connection.instance_variable_set(:@schema_cache, @schema_cache)
72
+ connection.instance_variable_set(:@schema_cache, @schema_cache) unless ::Rails.version >= '6'
64
73
  rescue ConnectionError
65
74
  raise if idx == configs.length - 1
66
75
  next
@@ -71,6 +80,14 @@ module Switchman
71
80
  end
72
81
  end
73
82
 
83
+ def get_schema_cache(_connection)
84
+ @schema_cache
85
+ end
86
+
87
+ def set_schema_cache(cache)
88
+ @schema_cache.copy_values(cache)
89
+ end
90
+
74
91
  %w{release_connection
75
92
  disconnect!
76
93
  flush!
@@ -98,6 +115,10 @@ module Switchman
98
115
  connection_pools.each { |pool| pool.clear_idle_connections!(since_when) }
99
116
  end
100
117
 
118
+ def remove_shard!(shard)
119
+ connection_pools.each { |pool| pool.remove_shard!(shard) }
120
+ end
121
+
101
122
  protected
102
123
 
103
124
  def connection_pools
@@ -105,7 +126,7 @@ module Switchman
105
126
  end
106
127
 
107
128
  def pool_key
108
- [active_shackles_environment,
129
+ [active_guard_rail_environment,
109
130
  active_shard.database_server.shareable? ? active_shard.database_server.pool_key : active_shard]
110
131
  end
111
132
 
@@ -113,7 +134,7 @@ module Switchman
113
134
  shard = active_shard
114
135
  unless config
115
136
  if shard != Shard.default
116
- config = shard.database_server.config(active_shackles_environment)
137
+ config = shard.database_server.config(active_guard_rail_environment)
117
138
  config = config.first if config.is_a?(Array)
118
139
  config = config.dup
119
140
  else
@@ -123,27 +144,28 @@ module Switchman
123
144
  # different models could be using different configs on the default
124
145
  # shard, and database server wouldn't know about that
125
146
  config = default_pool.spec.instance_variable_get(:@config)
126
- if config[active_shackles_environment].is_a?(Hash)
127
- config = config.merge(config[active_shackles_environment])
128
- elsif config[active_shackles_environment].is_a?(Array)
129
- config = config.merge(config[active_shackles_environment].first)
147
+ if config[active_guard_rail_environment].is_a?(Hash)
148
+ config = config.merge(config[active_guard_rail_environment])
149
+ elsif config[active_guard_rail_environment].is_a?(Array)
150
+ config = config.merge(config[active_guard_rail_environment].first)
130
151
  else
131
152
  config = config.dup
132
153
  end
133
154
  end
134
155
  end
135
- args = [config, "#{config[:adapter]}_connection"]
136
- args.unshift(pool_key.join("/"))
137
- spec = ::ActiveRecord::ConnectionAdapters::ConnectionSpecification.new(*args)
156
+ spec = ::ActiveRecord::ConnectionAdapters::ConnectionSpecification.new(
157
+ default_pool.spec.name,
158
+ config,
159
+ "#{config[:adapter]}_connection"
160
+ )
138
161
  # unfortunately the AR code that does this require logic can't really be
139
162
  # called in isolation
140
163
  require "active_record/connection_adapters/#{config[:adapter]}_adapter"
141
164
 
142
165
  ::ActiveRecord::ConnectionAdapters::ConnectionPool.new(spec).tap do |pool|
143
166
  pool.shard = shard
144
- if ::Rails.version >= '5.0.1'
145
- pool.enable_query_cache! if !@connection_pools.empty? && @connection_pools.first.last.query_cache_enabled
146
- end
167
+ pool.set_schema_cache(@schema_cache) if ::Rails.version >= '6'
168
+ pool.enable_query_cache! if !@connection_pools.empty? && @connection_pools.first.last.query_cache_enabled
147
169
  end
148
170
  end
149
171
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "securerandom"
2
4
 
3
5
  module Switchman
@@ -40,8 +42,14 @@ module Switchman
40
42
  def database_servers
41
43
  unless @database_servers
42
44
  @database_servers = {}.with_indifferent_access
43
- ::ActiveRecord::Base.configurations.each do |(id, config)|
44
- @database_servers[id] = DatabaseServer.new(id, config)
45
+ if ::Rails.version >= '6.0'
46
+ ::ActiveRecord::Base.configurations.configurations.each do |config|
47
+ @database_servers[config.env_name] = DatabaseServer.new(config.env_name, config.config)
48
+ end
49
+ else
50
+ ::ActiveRecord::Base.configurations.each do |(id, config)|
51
+ @database_servers[id] = DatabaseServer.new(id, config)
52
+ end
45
53
  end
46
54
  end
47
55
  @database_servers
@@ -63,12 +71,12 @@ module Switchman
63
71
  @fake
64
72
  end
65
73
 
66
- def config(environment = :master)
74
+ def config(environment = :primary)
67
75
  @configs[environment] ||= begin
68
76
  if @config[environment].is_a?(Array)
69
77
  @config[environment].map do |config|
70
78
  config = @config.merge((config || {}).symbolize_keys)
71
- # make sure Shackles doesn't get any brilliant ideas about choosing the first possible server
79
+ # make sure GuardRail doesn't get any brilliant ideas about choosing the first possible server
72
80
  config.delete(environment)
73
81
  config
74
82
  end
@@ -80,33 +88,33 @@ module Switchman
80
88
  end
81
89
  end
82
90
 
83
- def shackles_environment
84
- @shackles_environment || ::Shackles.environment
91
+ def guard_rail_environment
92
+ @guard_rail_environment || ::GuardRail.environment
85
93
  end
86
94
 
87
95
  # locks this db to a specific environment, except for
88
96
  # when doing writes (then it falls back to the current
89
- # value of Shackles.environment)
90
- def shackle!(environment = :slave)
91
- @shackles_environment = environment
97
+ # value of GuardRail.environment)
98
+ def guard!(environment = :secondary)
99
+ @guard_rail_environment = environment
92
100
  end
93
101
 
94
- def unshackle!
95
- @shackles_environment = nil
102
+ def unguard!
103
+ @guard_rail_environment = nil
96
104
  end
97
105
 
98
- def unshackle
99
- old_env = @shackles_environment
100
- unshackle!
106
+ def unguard
107
+ old_env = @guard_rail_environment
108
+ unguard!
101
109
  yield
102
110
  ensure
103
- shackle!(old_env)
111
+ guard!(old_env)
104
112
  end
105
113
 
106
114
  def shareable?
107
115
  @shareable_environment_key ||= []
108
- environment = shackles_environment
109
- explicit_user = ::Shackles.global_config[:username]
116
+ environment = guard_rail_environment
117
+ explicit_user = ::GuardRail.global_config[:username]
110
118
  return @shareable if @shareable_environment_key == [environment, explicit_user]
111
119
  @shareable_environment_key = [environment, explicit_user]
112
120
  if explicit_user
@@ -179,13 +187,18 @@ module Switchman
179
187
  shard = Shard.create!(:id => shard_id,
180
188
  :name => name,
181
189
  :database_server => self)
190
+ schema_already_existed = false
182
191
 
183
192
  begin
184
193
  self.class.creating_new_shard = true
185
194
  shard.activate(*Shard.categories) do
186
- ::Shackles.activate(:deploy) do
195
+ ::GuardRail.activate(:deploy) do
187
196
  begin
188
197
  if create_statement
198
+ if (::ActiveRecord::Base.connection.select_value("SELECT 1 FROM pg_namespace WHERE nspname=#{::ActiveRecord::Base.connection.quote(name)}"))
199
+ schema_already_existed = true
200
+ raise "This schema already exists; cannot overwrite"
201
+ end
189
202
  Array(create_statement.call).each do |stmt|
190
203
  ::ActiveRecord::Base.connection.execute(stmt)
191
204
  end
@@ -223,7 +236,9 @@ module Switchman
223
236
  shard
224
237
  rescue
225
238
  shard.destroy
226
- shard.drop_database rescue nil
239
+ unless schema_already_existed
240
+ shard.drop_database rescue nil
241
+ end
227
242
  reset_column_information unless create_schema == false rescue nil
228
243
  raise
229
244
  ensure
@@ -1,8 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'switchman/database_server'
2
4
 
3
5
  module Switchman
4
6
  class DefaultShard
5
7
  def id; 'default'; end
8
+ alias cache_key id
6
9
  def activate(*categories); yield; end
7
10
  def activate!(*categories); end
8
11
  def default?; true; end