switchman 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +8 -8
  2. data/app/models/switchman/shard.rb +10 -3
  3. data/lib/switchman/active_record/association.rb +36 -10
  4. data/lib/switchman/active_record/base.rb +9 -5
  5. data/lib/switchman/active_record/calculations.rb +12 -11
  6. data/lib/switchman/active_record/connection_handler.rb +102 -47
  7. data/lib/switchman/active_record/finder_methods.rb +1 -1
  8. data/lib/switchman/active_record/query_methods.rb +50 -25
  9. data/lib/switchman/active_record/relation.rb +4 -4
  10. data/lib/switchman/active_record/spawn_methods.rb +76 -29
  11. data/lib/switchman/connection_pool_proxy.rb +2 -1
  12. data/lib/switchman/database_server.rb +4 -2
  13. data/lib/switchman/engine.rb +1 -1
  14. data/lib/switchman/r_spec_helper.rb +14 -6
  15. data/lib/switchman/rails.rb +7 -9
  16. data/lib/switchman/test_helper.rb +4 -3
  17. data/lib/switchman/version.rb +1 -1
  18. data/spec/dummy/app/models/appendage.rb +2 -4
  19. data/spec/dummy/app/models/digit.rb +2 -4
  20. data/spec/dummy/app/models/feature.rb +0 -2
  21. data/spec/dummy/app/models/user.rb +0 -2
  22. data/spec/dummy/config/application.rb +0 -6
  23. data/spec/dummy/config/environments/development.rb +2 -9
  24. data/spec/dummy/config/environments/production.rb +2 -3
  25. data/spec/dummy/config/environments/test.rb +2 -11
  26. data/spec/dummy/config/routes.rb +2 -2
  27. data/spec/dummy/log/test.log +9947 -0
  28. data/spec/dummy/tmp/cache/375/530/shard%2F3200 +1 -0
  29. data/spec/dummy/tmp/cache/37A/590/shard%2F3214 +1 -0
  30. data/spec/dummy/tmp/cache/37E/620/shard%2F3182 +0 -0
  31. data/spec/dummy/tmp/cache/381/650/shard%2F3185 +1 -0
  32. data/spec/dummy/tmp/cache/382/650/shard%2F3177 +1 -0
  33. data/spec/dummy/tmp/cache/386/6B0/shard%2F3199 +1 -0
  34. data/spec/dummy/tmp/cache/3A6/F80/shard%2F13200 +1 -0
  35. data/spec/dummy/tmp/cache/3B0/080/shard%2F13183 +1 -0
  36. data/spec/dummy/tmp/cache/3B1/070/shard%2F13166 +1 -0
  37. data/spec/lib/active_record/association_spec.rb +21 -11
  38. data/spec/lib/active_record/finder_methods_spec.rb +1 -1
  39. data/spec/lib/active_record/query_cache_spec.rb +17 -17
  40. data/spec/lib/active_record/query_methods_spec.rb +6 -5
  41. data/spec/lib/active_record/relation_spec.rb +1 -1
  42. data/spec/lib/active_record/spawn_methods_spec.rb +5 -4
  43. data/spec/lib/connection_pool_proxy_spec.rb +2 -0
  44. data/spec/lib/database_server_spec.rb +12 -4
  45. data/spec/lib/shackles_spec.rb +8 -6
  46. metadata +61 -11
@@ -42,7 +42,7 @@ module Switchman
42
42
 
43
43
  activate { return true if connection.select_value(relation, "#{name} Exists") }
44
44
  false
45
- rescue ThrowResult
45
+ rescue ::ActiveRecord::ThrowResult
46
46
  false
47
47
  end
48
48
  end
@@ -12,36 +12,59 @@ module Switchman
12
12
  # for foreign key transposition
13
13
  # :to_a - a special value that Relation#to_a uses when querying multiple shards to
14
14
  # remove primary keys from conditions that aren't applicable to the current shard
15
- attr_accessor :shard_value, :shard_source_value
15
+ if ::Rails.version < '4'
16
+ attr_accessor :shard_value, :shard_source_value
17
+ else
18
+ def shard_value
19
+ @values[:shard]
20
+ end
21
+ def shard_source_value
22
+ @values[:shard_source]
23
+ end
24
+ def shard_value=(value)
25
+ raise ImmutableRelation if @loaded
26
+ @values[:shard] = value
27
+ end
28
+ def shard_source_value=(value)
29
+ raise ImmutableRelation if @loaded
30
+ @values[:shard_source] = value
31
+ end
32
+ end
16
33
 
17
34
  def shard(value, source = :explicit)
18
- relation = clone
19
- relation.shard_value = value
20
- relation.shard_source_value = source
21
- if (primary_shard != relation.primary_shard || source == :to_a)
22
- relation.where_values = relation.transpose_predicates(relation.where_values, primary_shard, relation.primary_shard, source == :to_a) if !relation.where_values.empty?
23
- relation.having_values = relation.transpose_predicates(relation.having_values, primary_shard, relation.primary_shard, source == :to_a) if !relation.having_values.empty?
35
+ (::Rails.version < '4' ? clone : spawn).shard!(value, source)
36
+ end
37
+
38
+ def shard!(value, source = :explicit)
39
+ primary_shard = self.primary_shard
40
+ self.shard_value = value
41
+ self.shard_source_value = source
42
+ if (primary_shard != self.primary_shard || source == :to_a)
43
+ self.where_values = transpose_predicates(where_values, primary_shard, self.primary_shard, source == :to_a) if !where_values.empty?
44
+ self.having_values = transpose_predicates(having_values, primary_shard, self.primary_shard, source == :to_a) if !having_values.empty?
24
45
  end
25
- relation
46
+ self
26
47
  end
27
48
 
28
- # replace these with versions that call build_where on the
29
- # result relation, not the source relation (so build_where
30
- # is able to implicitly change the shard_value)
31
- def where(opts, *rest)
32
- return self if opts.blank?
49
+ if ::Rails.version < '4'
50
+ # replace these with versions that call build_where on the
51
+ # result relation, not the source relation (so build_where
52
+ # is able to implicitly change the shard_value)
53
+ def where(opts, *rest)
54
+ return self if opts.blank?
33
55
 
34
- relation = clone
35
- relation.where_values += relation.build_where(opts, rest)
36
- relation
37
- end
56
+ relation = clone
57
+ relation.where_values += relation.build_where(opts, rest)
58
+ relation
59
+ end
38
60
 
39
- def having(opts, *rest)
40
- return self if opts.blank?
61
+ def having(opts, *rest)
62
+ return self if opts.blank?
41
63
 
42
- relation = clone
43
- relation.having_values += relation.build_where(opts, rest)
44
- relation
64
+ relation = clone
65
+ relation.having_values += relation.build_where(opts, rest)
66
+ relation
67
+ end
45
68
  end
46
69
 
47
70
  def build_where(opts, other = [])
@@ -147,6 +170,7 @@ module Switchman
147
170
  end
148
171
 
149
172
  def sharded_primary_key?(relation, column)
173
+ return column == 'id' if relation.engine == ::ActiveRecord::Base
150
174
  relation.engine.primary_key == column
151
175
  end
152
176
 
@@ -165,6 +189,7 @@ module Switchman
165
189
  attribute = attribute.relation if attribute.relation.is_a?(Arel::Nodes::TableAlias)
166
190
  [attribute.relation, column]
167
191
  end
192
+
168
193
  # semi-private
169
194
  public
170
195
  def transpose_predicates(predicates, source_shard, target_shard, remove_nonlocal_primary_keys = false)
@@ -194,11 +219,11 @@ module Switchman
194
219
  local_ids
195
220
  when Arel::Nodes::BindParam
196
221
  # look for a bind param with a matching column name
197
- if @bind_params && idx = @bind_params.find_index{|b| b.is_a?(Array) && b.first.try(:name) == predicate.left}
198
- column, value = @bind_params[idx]
222
+ if bind_values && idx = bind_values.find_index{|b| b.is_a?(Array) && b.first.try(:name) == predicate.left.name}
223
+ column, value = bind_values[idx]
199
224
  local_id = Shard.relative_id_for(value, current_source_shard, target_shard)
200
225
  local_id = [] if remove && local_id > Shard::IDS_PER_SHARD
201
- @bind_params[idx] = [column, local_id]
226
+ bind_values[idx] = [column, local_id]
202
227
  end
203
228
  predicate.right
204
229
  else
@@ -13,10 +13,10 @@ module Switchman
13
13
  end
14
14
  end
15
15
 
16
- def initialize_with_sharding(klass, table)
17
- initialize_without_sharding(klass, table)
18
- self.shard_value = Shard.current(klass.shard_category)
19
- self.shard_source_value = :implicit
16
+ def initialize_with_sharding(*args)
17
+ initialize_without_sharding(*args)
18
+ self.shard_value = Shard.current(klass.try(:shard_category) || :default) unless shard_value
19
+ self.shard_source_value = :implicit unless shard_source_value
20
20
  end
21
21
 
22
22
  def merge(*args)
@@ -1,17 +1,13 @@
1
1
  module Switchman
2
2
  module ActiveRecord
3
3
  module SpawnMethods
4
- def merge(r)
5
- return self unless r
6
- return to_a & r if r.is_a?(Array)
7
-
8
- # have to figure out shard stuff *before* conditions are merged
4
+ def shard_values_for_merge(r)
9
5
  if shard_value != r.shard_value
10
- if (r.shard_source_value == :implicit)
6
+ if r.shard_source_value == :implicit
11
7
  final_shard_value = shard_value
12
8
  final_primary_shard = primary_shard
13
9
  final_shard_source_value = shard_source_value
14
- elsif (shard_source_value == :implicit)
10
+ elsif shard_source_value == :implicit
15
11
  final_shard_value = r.shard_value
16
12
  final_primary_shard = r.primary_shard
17
13
  final_shard_source_value = r.shard_source_value
@@ -30,7 +26,6 @@ module Switchman
30
26
  final_primary_shard = Shard.default
31
27
  else
32
28
  final_shard_value = lhs_shard_value.to_a & rhs_shard_value.to_a
33
- return none if final_shard_value.length == 0
34
29
  final_primary_shard = final_shard_value.first
35
30
  final_shard_value = final_shard_value.first if final_shard_value.length == 1
36
31
  end
@@ -40,34 +35,86 @@ module Switchman
40
35
  shard_source_value == source_value || r.shard_source_value == source_value
41
36
  end
42
37
  raise "unknown shard_source_value" unless final_shard_source_value
43
-
44
- result = super
45
- result.shard_source_value = final_shard_source_value
46
- return result
47
38
  else
48
39
  # nothing fancy
49
- return super
50
40
  end
51
41
 
52
- # change the primary shard if necessary before merging
53
- result = if primary_shard != final_primary_shard && r.primary_shard != final_primary_shard
54
- lhs = shard(final_primary_shard)
55
- r = r.shard(final_primary_shard)
56
- lhs.merge(r)
57
- elsif primary_shard != final_primary_shard
58
- lhs = shard(final_primary_shard)
59
- lhs.merge(r)
60
- elsif r.primary_shard != final_primary_shard
61
- r = r.shard(final_primary_shard)
62
- super(r)
63
- else
64
- super
42
+ [final_shard_value, final_primary_shard, final_shard_source_value]
43
+ end
44
+
45
+ if ::Rails.version < '4'
46
+ def merge(r)
47
+ return self unless r
48
+ return super unless r.is_a?(Relation)
49
+
50
+ # have to figure out shard stuff *before* conditions are merged
51
+ final_shard_value, final_primary_shard, final_shard_source_value = shard_values_for_merge(r)
52
+
53
+ return super unless final_shard_source_value
54
+
55
+ if !final_shard_value
56
+ result = super
57
+ result.shard_source_value = final_shard_source_value
58
+ return result
59
+ end
60
+
61
+ return none if final_shard_value == []
62
+
63
+ # change the primary shard if necessary before merging
64
+ result = if primary_shard != final_primary_shard && r.primary_shard != final_primary_shard
65
+ lhs = shard(final_primary_shard)
66
+ r = r.shard(final_primary_shard)
67
+ lhs.merge(r)
68
+ elsif primary_shard != final_primary_shard
69
+ lhs = shard(final_primary_shard)
70
+ lhs.merge(r)
71
+ elsif r.primary_shard != final_primary_shard
72
+ r = r.shard(final_primary_shard)
73
+ super(r)
74
+ else
75
+ super
76
+ end
77
+
78
+ result.shard_value = final_shard_value
79
+ result.shard_source_value = final_shard_source_value
80
+
81
+ result
65
82
  end
83
+ else
84
+ def merge!(r)
85
+ # have to figure out shard stuff *before* conditions are merged
86
+ final_shard_value, final_primary_shard, final_shard_source_value = shard_values_for_merge(r)
87
+
88
+ return super unless final_shard_source_value
89
+
90
+ if !final_shard_value
91
+ super
92
+ self.shard_source_value = final_shard_source_value
93
+ return self
94
+ end
66
95
 
67
- result.shard_value = final_shard_value
68
- result.shard_source_value = final_shard_source_value
96
+ return none! if final_shard_value == []
69
97
 
70
- result
98
+ # change the primary shard if necessary before merging
99
+ if primary_shard != final_primary_shard && r.primary_shard != final_primary_shard
100
+ shard!(final_primary_shard)
101
+ r = r.shard(final_primary_shard)
102
+ super(r)
103
+ elsif primary_shard != final_primary_shard
104
+ shard!(final_primary_shard)
105
+ super(r)
106
+ elsif r.primary_shard != final_primary_shard
107
+ r = r.shard(final_primary_shard)
108
+ super(r)
109
+ else
110
+ super
111
+ end
112
+
113
+ self.shard_value = final_shard_value
114
+ self.shard_source_value = final_shard_source_value
115
+
116
+ self
117
+ end
71
118
  end
72
119
  end
73
120
  end
@@ -91,7 +91,8 @@ module Switchman
91
91
  end
92
92
  end
93
93
  end
94
- spec = ::ActiveRecord::Base::ConnectionSpecification.new(config, "#{config[:adapter]}_connection")
94
+ klass = ::Rails.version < '4' ? ::ActiveRecord::Base : ::ActiveRecord::ConnectionAdapters
95
+ spec = klass::ConnectionSpecification.new(config, "#{config[:adapter]}_connection")
95
96
  # unfortunately the AR code that does this require logic can't really be
96
97
  # called in isolation
97
98
  require "active_record/connection_adapters/#{config[:adapter]}_adapter"
@@ -125,7 +125,7 @@ module Switchman
125
125
  end
126
126
 
127
127
  def create_new_shard(options = {})
128
- raise MethodNotImplemented unless Shard.default.is_a?(Shard)
128
+ raise NotImplementedError.new("Cannot create new shards when sharding isn't initialized") unless Shard.default.is_a?(Shard)
129
129
 
130
130
  db_name = options[:db_name]
131
131
  create_schema = options[:schema]
@@ -193,13 +193,15 @@ module Switchman
193
193
  ::ActiveRecord::Base.connection.execute(stmt)
194
194
  end
195
195
  # have to disconnect and reconnect to the correct db
196
+ shard.name = db_name
196
197
  if self.shareable? && other_shard
197
198
  other_shard.activate { ::ActiveRecord::Base.connection }
198
199
  else
199
200
  ::ActiveRecord::Base.connection_pool.current_pool.disconnect!
200
201
  end
202
+ else
203
+ shard.name = db_name
201
204
  end
202
- shard.name = db_name
203
205
  old_proc = ::ActiveRecord::Base.connection.raw_connection.set_notice_processor {} if config[:adapter] == 'postgresql'
204
206
  old_verbose = ::ActiveRecord::Migration.verbose
205
207
  ::ActiveRecord::Migration.verbose = false
@@ -75,7 +75,7 @@ module Switchman
75
75
  ::ActiveRecord::Associations::Association.send(:include, ActiveRecord::Association)
76
76
  ::ActiveRecord::Associations::BelongsToAssociation.send(:include, ActiveRecord::BelongsToAssociation)
77
77
  ::ActiveRecord::Associations::CollectionProxy.send(:include, ActiveRecord::CollectionProxy)
78
- ::ActiveRecord::Associations::Builder::Association.send(:include, ActiveRecord::Builder::Association)
78
+ ::ActiveRecord::Associations::Builder::CollectionAssociation.send(:include, ActiveRecord::Builder::CollectionAssociation)
79
79
 
80
80
  ::ActiveRecord::Associations::Preloader::Association.send(:include, ActiveRecord::Preloader::Association)
81
81
  ::ActiveRecord::ConnectionAdapters::AbstractAdapter.send(:include, ActiveRecord::AbstractAdapter)
@@ -96,9 +96,13 @@ module Switchman
96
96
  shards.each do |shard|
97
97
  shard.activate do
98
98
  # this is how AR does it in fixtures.rb
99
- ::ActiveRecord::Base.connection.increment_open_transactions
100
- ::ActiveRecord::Base.connection.transaction_joinable = false
101
- ::ActiveRecord::Base.connection.begin_db_transaction
99
+ if ::Rails.version < '4'
100
+ ::ActiveRecord::Base.connection.increment_open_transactions
101
+ ::ActiveRecord::Base.connection.transaction_joinable = false
102
+ ::ActiveRecord::Base.connection.begin_db_transaction
103
+ else
104
+ ::ActiveRecord::Base.connection.begin_transaction joinable: false
105
+ end
102
106
  end
103
107
  end
104
108
  end
@@ -110,9 +114,13 @@ module Switchman
110
114
  shards << @shard1 unless @shard1.database_server == Shard.default.database_server
111
115
  shards.each do |shard|
112
116
  shard.activate do
113
- if ::ActiveRecord::Base.connection.open_transactions != 0
114
- ::ActiveRecord::Base.connection.rollback_db_transaction
115
- ::ActiveRecord::Base.connection.decrement_open_transactions
117
+ if ::Rails.version < '4'
118
+ if ::ActiveRecord::Base.connection.open_transactions != 0
119
+ ::ActiveRecord::Base.connection.rollback_db_transaction
120
+ ::ActiveRecord::Base.connection.decrement_open_transactions
121
+ end
122
+ else
123
+ ::ActiveRecord::Base.connection.rollback_transaction if ::ActiveRecord::Base.connection.transaction_open?
116
124
  end
117
125
  end
118
126
  end
@@ -1,21 +1,19 @@
1
1
  module Switchman::Rails
2
2
  module ClassMethods
3
- def cache_with_sharding
3
+ def cache
4
4
  Switchman::Shard.current.database_server.cache_store
5
5
  end
6
+ end
7
+
8
+ def self.included(klass)
9
+ klass.extend(ClassMethods)
10
+ klass.singleton_class.send(:remove_method, :cache)
6
11
 
7
12
  # in Rails 4+, the Rails.cache= method was used during bootstrap to set
8
13
  # Rails.cache(_without_sharding) to the value from the config file. but now
9
14
  # that that's done (the bootstrap happened before this module is included
10
15
  # into Rails), we want to make sure no one tries to assign to Rails.cache,
11
16
  # because it would be wrong w.r.t. sharding.
12
- def cache=(new_cache)
13
- raise NoMethodError
14
- end
15
- end
16
-
17
- def self.included(klass)
18
- klass.extend(ClassMethods)
19
- klass.singleton_class.alias_method_chain :cache, :sharding
17
+ klass.singleton_class.send(:remove_method, :cache=) if ::Rails.version >= '4'
20
18
  end
21
19
  end
@@ -5,9 +5,9 @@ module Switchman
5
5
  # recreate the default shard (it got buhleted)
6
6
  if Shard.default(true).is_a?(DefaultShard)
7
7
  begin
8
- Shard.create!(:default => true)
8
+ Shard.create!(default: true)
9
9
  rescue
10
- # database doesn't exist yet, presumably
10
+ # database doesn't exist yet, presumably cause we're creating it right now
11
11
  [nil, nil]
12
12
  end
13
13
  Shard.default(true)
@@ -15,7 +15,8 @@ module Switchman
15
15
 
16
16
  # can't auto-create a new shard on the default shard's db server if the
17
17
  # default shard is split across multiple db servers
18
- if ::ActiveRecord::Base.connection_handler.connection_pools.length > 1
18
+ if (::Rails.version < '4' ? ::ActiveRecord::Base.connection_handler.connection_pools.length :
19
+ ::ActiveRecord::Base.connection_handler.connection_pool_list.length) > 1
19
20
  server1 = DatabaseServer.create(:config => Shard.default.database_server.config)
20
21
  else
21
22
  server1 = Shard.default.database_server
@@ -1,3 +1,3 @@
1
1
  module Switchman
2
- VERSION = "1.1.0"
2
+ VERSION = "1.2.0"
3
3
  end
@@ -1,13 +1,11 @@
1
1
  class Appendage < ActiveRecord::Base
2
- attr_accessible :user, :user_id, :value
3
-
4
2
  belongs_to :user
5
3
  has_many :digits
6
4
 
7
5
  has_many :features, :as => :owner
8
6
 
9
- scope :has_no_value, where(:value => nil)
10
- scope :has_value, where("appendages.value IS NOT NULL")
7
+ scope :has_no_value, -> { where(:value => nil) }
8
+ scope :has_value, -> { where("appendages.value IS NOT NULL") }
11
9
 
12
10
  attr_writer :associated_shards
13
11
  class << self
@@ -1,9 +1,7 @@
1
1
  class Digit < ActiveRecord::Base
2
- attr_accessible :appendage, :appendage_id, :value
3
-
4
2
  belongs_to :appendage
5
3
  has_one :user, :through => :appendage
6
4
 
7
- scope :has_no_value, where(:value => nil)
8
- scope :has_value, where("digits.value IS NOT NULL")
5
+ scope :has_no_value, -> { where(:value => nil) }
6
+ scope :has_value, -> { where("digits.value IS NOT NULL") }
9
7
  end
@@ -1,5 +1,3 @@
1
1
  class Feature < ActiveRecord::Base
2
2
  belongs_to :owner, :polymorphic => true
3
-
4
- attr_accessible :owner, :value
5
3
  end