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
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
YzRjZGFmYzVmY2EwYjYyNGQ3Nzc0NzYxNjdhOTQ2M2U5ZDU1YmE0Zg==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
ZTY2ZDgwNDU2YjFmOWYwMDMyYzgyYzJmOGU5MWIzZTJlNGIzNDVhNg==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
NTVmOWYyY2Y2YWUzMmQ4MWY3NjUzZTFiYThmMmM0OGM3ZDA1YTczNDdmZjFl
|
10
|
+
MjEzYmJhNzBjODc4OGZmYzgwZmEwNDI3NWQ2YWU3Yzg3ODBkNTBmNTYyNThh
|
11
|
+
YTJlOGRjMDI5NzhjYmNhZmQyYTMwNWQ4MGIwOWQ2MDM4ODYwZDU=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
MzY2ODJiM2E3YWZkNjJhYjllODIyNDBkMzQ5NTkyNzhjNjY4Yzc5MDdiY2Iy
|
14
|
+
OGE3ZTA2N2U4YTdlZWFmMWJhZjQzNTU5NzNmYWYyMGIxZTNmOWVhZTc3Mzgw
|
15
|
+
MWE3YjYwNTlkODIzZThkNTlmMDI2NTVhNWNlNTA4Nzc5MTdlNjI=
|
@@ -16,8 +16,6 @@ module Switchman
|
|
16
16
|
private_constant :CATEGORIES
|
17
17
|
@shard_category = :unsharded
|
18
18
|
|
19
|
-
attr_accessible :name, :database_server, :default
|
20
|
-
|
21
19
|
# only allow one default
|
22
20
|
validates_uniqueness_of :default, :if => lambda { |s| s.default? }
|
23
21
|
|
@@ -87,7 +85,11 @@ module Switchman
|
|
87
85
|
nil
|
88
86
|
else
|
89
87
|
shard = Shard.new
|
90
|
-
|
88
|
+
if ::Rails.version < '4'
|
89
|
+
shard.assign_attributes(attributes, :without_protection => true)
|
90
|
+
else
|
91
|
+
shard.assign_attributes(attributes)
|
92
|
+
end
|
91
93
|
shard.instance_variable_set(:@new_record, false)
|
92
94
|
# connection info doesn't exist in database.yml;
|
93
95
|
# pretend the shard doesn't exist either
|
@@ -520,6 +522,11 @@ module Switchman
|
|
520
522
|
super
|
521
523
|
end
|
522
524
|
|
525
|
+
# skip global_id.hash
|
526
|
+
def hash
|
527
|
+
id.hash
|
528
|
+
end
|
529
|
+
|
523
530
|
private
|
524
531
|
|
525
532
|
def clear_cache
|
@@ -2,7 +2,8 @@ module Switchman
|
|
2
2
|
module ActiveRecord
|
3
3
|
module Association
|
4
4
|
def self.included(klass)
|
5
|
-
%w{build_record creation_attributes load_target
|
5
|
+
%w{build_record creation_attributes load_target scope}.each do |method|
|
6
|
+
method = 'scoped' if method == 'scope' && ::Rails.version < '4'
|
6
7
|
klass.alias_method_chain(method, :sharding)
|
7
8
|
end
|
8
9
|
end
|
@@ -24,10 +25,14 @@ module Switchman
|
|
24
25
|
self.shard.activate { load_target_without_sharding }
|
25
26
|
end
|
26
27
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
28
|
+
# scoped is renamed to scope in Rails 4
|
29
|
+
method = ::Rails.version < '4' ? 'scoped' : 'scope'
|
30
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
31
|
+
def #{method}_with_sharding
|
32
|
+
shard_value = @reflection.options[:multishard] ? @owner : self.shard
|
33
|
+
@owner.shard.activate { #{method}_without_sharding.shard(shard_value, :association) }
|
34
|
+
end
|
35
|
+
RUBY
|
31
36
|
|
32
37
|
def creation_attributes_with_sharding
|
33
38
|
attributes = creation_attributes_without_sharding
|
@@ -65,9 +70,17 @@ module Switchman
|
|
65
70
|
end
|
66
71
|
|
67
72
|
module Builder
|
68
|
-
module
|
73
|
+
module CollectionAssociation
|
69
74
|
def self.included(klass)
|
70
|
-
|
75
|
+
if ::Rails.version < '4'
|
76
|
+
[klass] + klass.descendants.each do |k|
|
77
|
+
k.valid_options << :multishard
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def valid_options
|
83
|
+
super + [:multishard]
|
71
84
|
end
|
72
85
|
end
|
73
86
|
end
|
@@ -77,7 +90,11 @@ module Switchman
|
|
77
90
|
def self.included(klass)
|
78
91
|
klass.send(:remove_method, :associated_records_by_owner)
|
79
92
|
klass.send(:remove_method, :owners_by_key)
|
80
|
-
|
93
|
+
if ::Rails.version < '4'
|
94
|
+
klass.send(:remove_method, :scoped)
|
95
|
+
else
|
96
|
+
klass.send(:remove_method, :scope)
|
97
|
+
end
|
81
98
|
end
|
82
99
|
|
83
100
|
def associated_records_by_owner
|
@@ -126,15 +143,24 @@ module Switchman
|
|
126
143
|
end
|
127
144
|
end
|
128
145
|
|
129
|
-
def
|
146
|
+
def scope
|
130
147
|
build_scope
|
131
148
|
end
|
149
|
+
# renamed to just scope in Rails 4
|
150
|
+
if ::Rails.version < '4'
|
151
|
+
alias_method :scoped, :scope
|
152
|
+
remove_method(:scope)
|
153
|
+
end
|
132
154
|
end
|
133
155
|
end
|
134
156
|
|
135
157
|
module CollectionProxy
|
136
158
|
def shard(*args)
|
137
|
-
|
159
|
+
if ::Rails.version < '4'
|
160
|
+
scoped.shard(*args)
|
161
|
+
else
|
162
|
+
scope.shard(*args)
|
163
|
+
end
|
138
164
|
end
|
139
165
|
end
|
140
166
|
end
|
@@ -2,7 +2,11 @@ module Switchman
|
|
2
2
|
module ActiveRecord
|
3
3
|
module Base
|
4
4
|
module ClassMethods
|
5
|
-
|
5
|
+
if ::Rails.version < '4'
|
6
|
+
delegate :shard, to: :scoped
|
7
|
+
else
|
8
|
+
delegate :shard, to: :all
|
9
|
+
end
|
6
10
|
|
7
11
|
def shard_category
|
8
12
|
@shard_category || :default
|
@@ -29,7 +33,7 @@ module Switchman
|
|
29
33
|
end
|
30
34
|
|
31
35
|
def transaction(*args)
|
32
|
-
if current_scope
|
36
|
+
if self != ::ActiveRecord::Base && current_scope
|
33
37
|
current_scope.activate do
|
34
38
|
db = Shard.current(shard_category).database_server
|
35
39
|
if ::Shackles.environment != db.shackles_environment
|
@@ -78,16 +82,16 @@ module Switchman
|
|
78
82
|
|
79
83
|
def save(*args)
|
80
84
|
@shard_set_in_stone = true
|
81
|
-
self.class.
|
85
|
+
self.class.shard(shard).scoping { super }
|
82
86
|
end
|
83
87
|
|
84
88
|
def save!(*args)
|
85
89
|
@shard_set_in_stone = true
|
86
|
-
self.class.
|
90
|
+
self.class.shard(shard).scoping { super }
|
87
91
|
end
|
88
92
|
|
89
93
|
def destroy
|
90
|
-
self.class.
|
94
|
+
self.class.shard(shard).scoping { super }
|
91
95
|
end
|
92
96
|
|
93
97
|
def clone
|
@@ -45,15 +45,15 @@ module Switchman
|
|
45
45
|
relation.select_values = [operation_over_aggregate_column(column, "average", distinct).as("average"),
|
46
46
|
operation_over_aggregate_column(column, "count", distinct).as("count")]
|
47
47
|
|
48
|
-
initial_results = relation.activate{ |rel|
|
48
|
+
initial_results = relation.activate{ |rel| klass.connection.select_all(rel) }
|
49
49
|
if initial_results.is_a?(Array)
|
50
50
|
initial_results.each do |r|
|
51
|
-
r["average"] = type_cast_calculated_value(r["average"],
|
52
|
-
r["count"] = type_cast_calculated_value(r["count"],
|
51
|
+
r["average"] = type_cast_calculated_value(r["average"], column_for(column_name), "average")
|
52
|
+
r["count"] = type_cast_calculated_value(r["count"], column_for(column_name), "count")
|
53
53
|
end
|
54
54
|
result = initial_results.map{|r| r["average"] * r["count"]}.sum / initial_results.map{|r| r["count"]}.sum
|
55
55
|
else
|
56
|
-
result = type_cast_calculated_value(initial_results["average"],
|
56
|
+
result = type_cast_calculated_value(initial_results.first["average"], column_for(column_name), "average")
|
57
57
|
end
|
58
58
|
result
|
59
59
|
end
|
@@ -66,7 +66,7 @@ module Switchman
|
|
66
66
|
target_shard = Shard.current(:default)
|
67
67
|
|
68
68
|
rows = relation.activate do |rel, shard|
|
69
|
-
calculated_data =
|
69
|
+
calculated_data = klass.connection.select_all(rel)
|
70
70
|
|
71
71
|
if opts[:association]
|
72
72
|
key_ids = calculated_data.collect { |row| row[opts[:group_aliases].first] }
|
@@ -98,9 +98,9 @@ module Switchman
|
|
98
98
|
opts = {:operation => operation, :column_name => column_name, :distinct => distinct}
|
99
99
|
|
100
100
|
opts[:aggregate_alias] = aggregate_alias_for(operation, column_name)
|
101
|
-
group_attrs =
|
101
|
+
group_attrs = group_values
|
102
102
|
if group_attrs.first.respond_to?(:to_sym)
|
103
|
-
association =
|
103
|
+
association = klass.reflect_on_association(group_attrs.first.to_sym)
|
104
104
|
associated = group_attrs.size == 1 && association && association.macro == :belongs_to # only count belongs_to associations
|
105
105
|
group_fields = Array(associated ? association.foreign_key : group_attrs)
|
106
106
|
else
|
@@ -125,12 +125,12 @@ module Switchman
|
|
125
125
|
elsif operation == 'average'
|
126
126
|
'average'
|
127
127
|
else
|
128
|
-
column_alias_for(operation
|
128
|
+
column_alias_for("#{operation} #{column_name}")
|
129
129
|
end
|
130
130
|
end
|
131
131
|
|
132
132
|
def build_grouped_calculation_relation(opts)
|
133
|
-
group =
|
133
|
+
group = opts[:group_fields]
|
134
134
|
|
135
135
|
select_values = [
|
136
136
|
operation_over_aggregate_column(
|
@@ -146,7 +146,7 @@ module Switchman
|
|
146
146
|
'count', opts[:distinct]).as('count')
|
147
147
|
end
|
148
148
|
|
149
|
-
select_values +=
|
149
|
+
select_values += select_values unless having_values.empty?
|
150
150
|
select_values.concat opts[:group_fields].zip(opts[:group_aliases]).map { |field,aliaz|
|
151
151
|
if field.respond_to?(:as)
|
152
152
|
field.as(aliaz)
|
@@ -155,7 +155,8 @@ module Switchman
|
|
155
155
|
end
|
156
156
|
}
|
157
157
|
|
158
|
-
relation = except(:group)
|
158
|
+
relation = except(:group)
|
159
|
+
relation.group_values = group
|
159
160
|
relation.select_values = select_values
|
160
161
|
relation
|
161
162
|
end
|
@@ -22,10 +22,11 @@ module Switchman
|
|
22
22
|
def self.included(klass)
|
23
23
|
klass.alias_method_chain(:establish_connection, :sharding)
|
24
24
|
klass.alias_method_chain(:remove_connection, :sharding)
|
25
|
+
klass.send(:remove_method, :retrieve_connection_pool) if ::Rails.version >= '4'
|
25
26
|
end
|
26
27
|
|
27
|
-
def establish_connection_with_sharding(
|
28
|
-
establish_connection_without_sharding(
|
28
|
+
def establish_connection_with_sharding(owner, spec)
|
29
|
+
establish_connection_without_sharding(owner, spec)
|
29
30
|
|
30
31
|
# this is the first place that the adapter would have been required; but now we
|
31
32
|
# need this addition ASAP since it will be called when loading the default shard below
|
@@ -34,8 +35,16 @@ module Switchman
|
|
34
35
|
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:include, ActiveRecord::PostgreSQLAdapter)
|
35
36
|
end
|
36
37
|
|
37
|
-
|
38
|
-
|
38
|
+
# AR3 uses the name, AR4 uses the model
|
39
|
+
model = case owner
|
40
|
+
when String
|
41
|
+
owner.constantize
|
42
|
+
when Class
|
43
|
+
owner
|
44
|
+
else
|
45
|
+
raise "unknown owner #{owner}"
|
46
|
+
end
|
47
|
+
pool = ::Rails.version < '4' ? connection_pools[spec] : owner_to_pool[owner.name]
|
39
48
|
|
40
49
|
first_time = !Shard.instance_variable_get(:@default)
|
41
50
|
if first_time
|
@@ -52,7 +61,12 @@ module Switchman
|
|
52
61
|
proxy = ConnectionPoolProxy.new(model.shard_category,
|
53
62
|
pool,
|
54
63
|
@shard_connection_pools)
|
55
|
-
|
64
|
+
if ::Rails.version < '4'
|
65
|
+
connection_pools[spec] = proxy
|
66
|
+
else
|
67
|
+
owner_to_pool[owner.name] = proxy
|
68
|
+
class_to_pool.clear
|
69
|
+
end
|
56
70
|
|
57
71
|
if first_time
|
58
72
|
if Shard.default.database_server.config[:prefer_slave]
|
@@ -65,8 +79,10 @@ module Switchman
|
|
65
79
|
end
|
66
80
|
end
|
67
81
|
|
68
|
-
|
69
|
-
|
82
|
+
if ::Rails.version < '4'
|
83
|
+
initialize_categories(model)
|
84
|
+
class_to_pool[model.name] = proxy
|
85
|
+
end
|
70
86
|
|
71
87
|
# reload the default shard if we just got a new connection
|
72
88
|
# to where the Shards table is
|
@@ -96,61 +112,100 @@ module Switchman
|
|
96
112
|
end
|
97
113
|
|
98
114
|
def remove_connection_with_sharding(model)
|
99
|
-
uninitialize_ar(model) if
|
100
|
-
remove_connection_without_sharding(model)
|
115
|
+
uninitialize_ar(model) if (::Rails.version < '4' ? class_to_pool : owner_to_pool)[model.name].is_a?(ConnectionPoolProxy)
|
116
|
+
result = remove_connection_without_sharding(model)
|
117
|
+
initialize_categories if ::Rails.version >= '4'
|
118
|
+
result
|
119
|
+
end
|
120
|
+
|
121
|
+
if ::Rails.version >= '4'
|
122
|
+
def retrieve_connection_pool(klass)
|
123
|
+
class_to_pool[klass.name] ||= begin
|
124
|
+
original_klass = klass
|
125
|
+
until pool = pool_for(klass)
|
126
|
+
klass = klass.superclass
|
127
|
+
break unless klass <= Base
|
128
|
+
end
|
129
|
+
|
130
|
+
if pool.is_a?(ConnectionPoolProxy) && pool.category != original_klass.shard_category
|
131
|
+
default_pool = pool.default_pool
|
132
|
+
pool = nil
|
133
|
+
class_to_pool.each_value { |p| pool = p if p.is_a?(ConnectionPoolProxy) &&
|
134
|
+
p.category == original_klass.shard_category &&
|
135
|
+
p.default_pool == default_pool }
|
136
|
+
pool ||= ConnectionPoolProxy.new(original_klass.shard_category, default_pool, @shard_connection_pools)
|
137
|
+
end
|
138
|
+
|
139
|
+
class_to_pool[original_klass.name] = pool
|
140
|
+
end
|
141
|
+
end
|
101
142
|
end
|
102
143
|
|
103
144
|
private
|
104
145
|
|
146
|
+
# AR3 only; AR4 defines it, and hides this version,
|
147
|
+
# and it's a slightly different data structure
|
148
|
+
def class_to_pool
|
149
|
+
@class_to_pool
|
150
|
+
end
|
151
|
+
|
105
152
|
def uninitialize_ar(model = ::ActiveRecord::Base)
|
106
153
|
# take the proxies out
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
154
|
+
if ::Rails.version >= '4'
|
155
|
+
owner_to_pool[model.name] = owner_to_pool[model.name].default_pool
|
156
|
+
else
|
157
|
+
class_to_pool.each_key do |model_name|
|
158
|
+
pool_model = model_name.constantize
|
159
|
+
# only de-proxify models that inherit from what we're uninitializing
|
160
|
+
next unless pool_model == model || pool_model < model
|
161
|
+
proxy = class_to_pool[model_name]
|
162
|
+
next unless proxy.is_a?(ConnectionPoolProxy)
|
163
|
+
|
164
|
+
# make sure we're switched back to the default shard for the
|
165
|
+
# connection that will remain
|
166
|
+
if proxy.connected?
|
167
|
+
Shard.default.activate(proxy.category) { proxy.connection }
|
168
|
+
end
|
169
|
+
connection_pools[proxy.spec] = proxy.default_pool
|
170
|
+
class_to_pool[model_name] = proxy.default_pool
|
118
171
|
end
|
119
|
-
connection_pools[proxy.spec] = proxy.default_pool
|
120
|
-
@class_to_pool[model_name] = proxy.default_pool
|
121
|
-
end
|
122
172
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
173
|
+
# prune dups that were created for implementing shard categories
|
174
|
+
class_to_pool.each_key do |model_name|
|
175
|
+
next if model_name == ::ActiveRecord::Base.name
|
176
|
+
pool_model = model_name.constantize
|
177
|
+
class_to_pool.delete(model_name) if retrieve_connection_pool(pool_model.superclass) == class_to_pool[model_name]
|
178
|
+
end
|
128
179
|
end
|
129
180
|
end
|
130
181
|
|
131
182
|
# semi-private
|
132
183
|
public
|
133
184
|
def initialize_categories(model = ::ActiveRecord::Base)
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
185
|
+
if ::Rails.version < '4'
|
186
|
+
# now set up pools for models that inherit from this model, but with a different
|
187
|
+
# sharding category
|
188
|
+
Shard.const_get(:CATEGORIES).each do |category, models|
|
189
|
+
next if category == :default
|
190
|
+
next if category == model.shard_category
|
191
|
+
|
192
|
+
this_proxy = nil
|
193
|
+
Array(models).each do |category_model|
|
194
|
+
category_model = category_model.constantize if category_model.is_a? String
|
195
|
+
next unless category_model < model
|
196
|
+
|
197
|
+
# don't replace existing connections
|
198
|
+
next if class_to_pool[category_model.name]
|
199
|
+
|
200
|
+
default_pool = retrieve_connection_pool(model)
|
201
|
+
default_pool = default_pool.default_pool if default_pool.is_a?(ConnectionPoolProxy)
|
202
|
+
# look for an existing compatible proxy for this category
|
203
|
+
this_proxy ||= ConnectionPoolProxy.new(category_model.shard_category, default_pool, @shard_connection_pools)
|
204
|
+
class_to_pool[category_model.name] = this_proxy
|
205
|
+
end
|
153
206
|
end
|
207
|
+
else
|
208
|
+
class_to_pool.clear
|
154
209
|
end
|
155
210
|
end
|
156
211
|
end
|