switchman 1.1.0 → 1.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.
- checksums.yaml +8 -8
- data/app/models/switchman/shard.rb +10 -3
- data/lib/switchman/active_record/association.rb +36 -10
- data/lib/switchman/active_record/base.rb +9 -5
- data/lib/switchman/active_record/calculations.rb +12 -11
- data/lib/switchman/active_record/connection_handler.rb +102 -47
- data/lib/switchman/active_record/finder_methods.rb +1 -1
- data/lib/switchman/active_record/query_methods.rb +50 -25
- data/lib/switchman/active_record/relation.rb +4 -4
- data/lib/switchman/active_record/spawn_methods.rb +76 -29
- data/lib/switchman/connection_pool_proxy.rb +2 -1
- data/lib/switchman/database_server.rb +4 -2
- data/lib/switchman/engine.rb +1 -1
- data/lib/switchman/r_spec_helper.rb +14 -6
- data/lib/switchman/rails.rb +7 -9
- data/lib/switchman/test_helper.rb +4 -3
- data/lib/switchman/version.rb +1 -1
- data/spec/dummy/app/models/appendage.rb +2 -4
- data/spec/dummy/app/models/digit.rb +2 -4
- data/spec/dummy/app/models/feature.rb +0 -2
- data/spec/dummy/app/models/user.rb +0 -2
- data/spec/dummy/config/application.rb +0 -6
- data/spec/dummy/config/environments/development.rb +2 -9
- data/spec/dummy/config/environments/production.rb +2 -3
- data/spec/dummy/config/environments/test.rb +2 -11
- data/spec/dummy/config/routes.rb +2 -2
- data/spec/dummy/log/test.log +9947 -0
- data/spec/dummy/tmp/cache/375/530/shard%2F3200 +1 -0
- data/spec/dummy/tmp/cache/37A/590/shard%2F3214 +1 -0
- data/spec/dummy/tmp/cache/37E/620/shard%2F3182 +0 -0
- data/spec/dummy/tmp/cache/381/650/shard%2F3185 +1 -0
- data/spec/dummy/tmp/cache/382/650/shard%2F3177 +1 -0
- data/spec/dummy/tmp/cache/386/6B0/shard%2F3199 +1 -0
- data/spec/dummy/tmp/cache/3A6/F80/shard%2F13200 +1 -0
- data/spec/dummy/tmp/cache/3B0/080/shard%2F13183 +1 -0
- data/spec/dummy/tmp/cache/3B1/070/shard%2F13166 +1 -0
- data/spec/lib/active_record/association_spec.rb +21 -11
- data/spec/lib/active_record/finder_methods_spec.rb +1 -1
- data/spec/lib/active_record/query_cache_spec.rb +17 -17
- data/spec/lib/active_record/query_methods_spec.rb +6 -5
- data/spec/lib/active_record/relation_spec.rb +1 -1
- data/spec/lib/active_record/spawn_methods_spec.rb +5 -4
- data/spec/lib/connection_pool_proxy_spec.rb +2 -0
- data/spec/lib/database_server_spec.rb +12 -4
- data/spec/lib/shackles_spec.rb +8 -6
- metadata +61 -11
@@ -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
|
-
|
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
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
46
|
+
self
|
26
47
|
end
|
27
48
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
56
|
+
relation = clone
|
57
|
+
relation.where_values += relation.build_where(opts, rest)
|
58
|
+
relation
|
59
|
+
end
|
38
60
|
|
39
|
-
|
40
|
-
|
61
|
+
def having(opts, *rest)
|
62
|
+
return self if opts.blank?
|
41
63
|
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
198
|
-
column, value =
|
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
|
-
|
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(
|
17
|
-
initialize_without_sharding(
|
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
|
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
|
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
|
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
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
-
|
68
|
-
result.shard_source_value = final_shard_source_value
|
96
|
+
return none! if final_shard_value == []
|
69
97
|
|
70
|
-
|
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
|
-
|
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
|
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
|
data/lib/switchman/engine.rb
CHANGED
@@ -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::
|
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
|
-
::
|
100
|
-
|
101
|
-
|
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 ::
|
114
|
-
::ActiveRecord::Base.connection.
|
115
|
-
|
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
|
data/lib/switchman/rails.rb
CHANGED
@@ -1,21 +1,19 @@
|
|
1
1
|
module Switchman::Rails
|
2
2
|
module ClassMethods
|
3
|
-
def
|
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
|
-
|
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!(:
|
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
|
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
|
data/lib/switchman/version.rb
CHANGED
@@ -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
|