ar-octopus 0.8.5 → 0.10.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.travis.yml +6 -9
  4. data/Appraisals +8 -8
  5. data/README.mkdn +47 -15
  6. data/Rakefile +1 -0
  7. data/ar-octopus.gemspec +9 -12
  8. data/gemfiles/rails42.gemfile +2 -2
  9. data/gemfiles/{rails32.gemfile → rails5.gemfile} +2 -2
  10. data/gemfiles/{rails4.gemfile → rails51.gemfile} +2 -2
  11. data/gemfiles/{rails41.gemfile → rails52.gemfile} +2 -2
  12. data/lib/octopus/abstract_adapter.rb +4 -10
  13. data/lib/octopus/association.rb +1 -0
  14. data/lib/octopus/association_shard_tracking.rb +41 -71
  15. data/lib/octopus/collection_association.rb +9 -3
  16. data/lib/octopus/exception.rb +4 -0
  17. data/lib/octopus/finder_methods.rb +8 -0
  18. data/lib/octopus/load_balancing/round_robin.rb +2 -1
  19. data/lib/octopus/log_subscriber.rb +6 -2
  20. data/lib/octopus/migration.rb +123 -55
  21. data/lib/octopus/model.rb +42 -27
  22. data/lib/octopus/persistence.rb +33 -27
  23. data/lib/octopus/proxy.rb +147 -272
  24. data/lib/octopus/proxy_config.rb +251 -0
  25. data/lib/octopus/query_cache_for_shards.rb +24 -0
  26. data/lib/octopus/railtie.rb +0 -2
  27. data/lib/octopus/relation_proxy.rb +36 -1
  28. data/lib/octopus/result_patch.rb +19 -0
  29. data/lib/octopus/scope_proxy.rb +12 -5
  30. data/lib/octopus/shard_tracking.rb +8 -3
  31. data/lib/octopus/slave_group.rb +3 -3
  32. data/lib/octopus/version.rb +1 -1
  33. data/lib/octopus.rb +71 -18
  34. data/spec/config/shards.yml +12 -0
  35. data/spec/migrations/10_create_users_using_replication.rb +1 -1
  36. data/spec/migrations/11_add_field_in_all_slaves.rb +1 -1
  37. data/spec/migrations/12_create_users_using_block.rb +1 -1
  38. data/spec/migrations/13_create_users_using_block_and_using.rb +1 -1
  39. data/spec/migrations/14_create_users_on_shards_of_a_group_with_versions.rb +1 -1
  40. data/spec/migrations/15_create_user_on_shards_of_default_group_with_versions.rb +1 -1
  41. data/spec/migrations/1_create_users_on_master.rb +1 -1
  42. data/spec/migrations/2_create_users_on_canada.rb +1 -1
  43. data/spec/migrations/3_create_users_on_both_shards.rb +1 -1
  44. data/spec/migrations/4_create_users_on_shards_of_a_group.rb +1 -1
  45. data/spec/migrations/5_create_users_on_multiples_groups.rb +1 -1
  46. data/spec/migrations/6_raise_exception_with_invalid_shard_name.rb +1 -1
  47. data/spec/migrations/7_raise_exception_with_invalid_multiple_shard_names.rb +1 -1
  48. data/spec/migrations/8_raise_exception_with_invalid_group_name.rb +1 -1
  49. data/spec/migrations/9_raise_exception_with_multiple_invalid_group_names.rb +1 -1
  50. data/spec/octopus/association_shard_tracking_spec.rb +344 -16
  51. data/spec/octopus/load_balancing/round_robin_spec.rb +15 -0
  52. data/spec/octopus/log_subscriber_spec.rb +1 -1
  53. data/spec/octopus/migration_spec.rb +45 -11
  54. data/spec/octopus/model_spec.rb +204 -16
  55. data/spec/octopus/octopus_spec.rb +2 -2
  56. data/spec/octopus/proxy_spec.rb +44 -40
  57. data/spec/octopus/query_cache_for_shards_spec.rb +40 -0
  58. data/spec/octopus/relation_proxy_spec.rb +71 -23
  59. data/spec/octopus/replicated_slave_grouped_spec.rb +27 -0
  60. data/spec/octopus/replication_spec.rb +72 -2
  61. data/spec/octopus/scope_proxy_spec.rb +41 -7
  62. data/spec/spec_helper.rb +2 -0
  63. data/spec/support/database_connection.rb +1 -1
  64. data/spec/support/database_models.rb +1 -1
  65. data/spec/support/octopus_helper.rb +14 -6
  66. data/spec/tasks/octopus.rake_spec.rb +1 -1
  67. metadata +40 -30
  68. data/.ruby-version +0 -1
  69. data/init.rb +0 -1
  70. data/lib/octopus/has_and_belongs_to_many_association.rb +0 -9
  71. data/rails/init.rb +0 -1
data/lib/octopus/proxy.rb CHANGED
@@ -4,196 +4,64 @@ require 'octopus/load_balancing/round_robin'
4
4
 
5
5
  module Octopus
6
6
  class Proxy
7
- attr_accessor :config, :sharded
7
+ attr_accessor :proxy_config
8
+
9
+ delegate :current_model, :current_model=,
10
+ :current_shard, :current_shard=,
11
+ :current_group, :current_group=,
12
+ :current_slave_group, :current_slave_group=,
13
+ :current_load_balance_options, :current_load_balance_options=,
14
+ :block, :block=, :fully_replicated?, :has_group?,
15
+ :shard_names, :shards_for_group, :shards, :sharded, :slaves_list,
16
+ :shards_slave_groups, :slave_groups, :replicated, :slaves_load_balancer,
17
+ :config, :initialize_shards, :shard_name, to: :proxy_config, prefix: false
8
18
 
9
19
  def initialize(config = Octopus.config)
10
- initialize_shards(config)
11
- initialize_replication(config) if !config.nil? && config['replicated']
20
+ self.proxy_config = Octopus::ProxyConfig.new(config)
12
21
  end
13
22
 
14
- def initialize_shards(config)
15
- @shards = HashWithIndifferentAccess.new
16
- @shards_slave_groups = HashWithIndifferentAccess.new
17
- @slave_groups = HashWithIndifferentAccess.new
18
- @groups = {}
19
- @adapters = Set.new
20
- @config = ActiveRecord::Base.connection_pool_without_octopus.spec.config
23
+ # Rails Connection Methods - Those methods are overriden to add custom behavior that helps
24
+ # Octopus introduce Sharding / Replication.
25
+ delegate :adapter_name, :add_transaction_record, :case_sensitive_modifier,
26
+ :type_cast, :to_sql, :quote, :quote_column_name, :quote_table_name,
27
+ :quote_table_name_for_assignment, :supports_migrations?, :table_alias_for,
28
+ :table_exists?, :in_clause_length, :supports_ddl_transactions?,
29
+ :sanitize_limit, :prefetch_primary_key?, :current_database,
30
+ :combine_bind_parameters, :empty_insert_statement_value, :assume_migrated_upto_version,
31
+ :schema_cache, :substitute_at, :internal_string_options_for_primary_key, :lookup_cast_type_from_column,
32
+ :supports_advisory_locks?, :get_advisory_lock, :initialize_internal_metadata_table,
33
+ :release_advisory_lock, :prepare_binds_for_database, :cacheable_query, :column_name_for_operation,
34
+ :prepared_statements, :transaction_state, :create_table, to: :select_connection
21
35
 
22
- unless config.nil?
23
- @entire_sharded = config['entire_sharded']
24
- @shards_config = config[Octopus.rails_env]
25
- end
26
-
27
- @shards_config ||= []
28
-
29
- @shards_config.each do |key, value|
30
- if value.is_a?(String)
31
- value = resolve_string_connection(value).merge(:octopus_shard => key)
32
- initialize_adapter(value['adapter'])
33
- @shards[key.to_sym] = connection_pool_for(value, "#{value['adapter']}_connection")
34
- elsif value.is_a?(Hash) && value.key?('adapter')
35
- value.merge!(:octopus_shard => key)
36
- initialize_adapter(value['adapter'])
37
- @shards[key.to_sym] = connection_pool_for(value, "#{value['adapter']}_connection")
38
-
39
- slave_group_configs = value.select do |_k, v|
40
- structurally_slave_group? v
41
- end
42
-
43
- if slave_group_configs.present?
44
- slave_groups = HashWithIndifferentAccess.new
45
- slave_group_configs.each do |slave_group_name, slave_configs|
46
- slaves = HashWithIndifferentAccess.new
47
- slave_configs.each do |slave_name, slave_config|
48
- @shards[slave_name.to_sym] = connection_pool_for(slave_config, "#{value['adapter']}_connection")
49
- slaves[slave_name.to_sym] = slave_name.to_sym
50
- end
51
- slave_groups[slave_group_name.to_sym] = Octopus::SlaveGroup.new(slaves)
52
- end
53
- @shards_slave_groups[key.to_sym] = slave_groups
54
- @sharded = true
55
- end
56
- elsif value.is_a?(Hash)
57
- @groups[key.to_s] = []
58
-
59
- value.each do |k, v|
60
- fail 'You have duplicated shard names!' if @shards.key?(k.to_sym)
61
-
62
- initialize_adapter(v['adapter'])
63
- config_with_octopus_shard = v.merge(:octopus_shard => k)
64
-
65
- @shards[k.to_sym] = connection_pool_for(config_with_octopus_shard, "#{v['adapter']}_connection")
66
- @groups[key.to_s] << k.to_sym
67
- end
68
-
69
- if structurally_slave_group? value
70
- slaves = Hash[@groups[key.to_s].map { |v| [v, v] }]
71
- @slave_groups[key.to_sym] = Octopus::SlaveGroup.new(slaves)
72
- end
73
- end
74
- end
75
-
76
- @shards[:master] ||= ActiveRecord::Base.connection_pool_without_octopus
77
- end
78
-
79
- def initialize_replication(config)
80
- @replicated = true
81
- if config.key?('fully_replicated')
82
- @fully_replicated = config['fully_replicated']
83
- else
84
- @fully_replicated = true
85
- end
86
-
87
- @slaves_list = @shards.keys.map(&:to_s).sort
88
- @slaves_list.delete('master')
89
- @slaves_load_balancer = Octopus::LoadBalancing::RoundRobin.new(@slaves_list)
90
- end
91
-
92
- def current_model
93
- Thread.current['octopus.current_model']
94
- end
95
-
96
- def current_model=(model)
97
- Thread.current['octopus.current_model'] = model.is_a?(ActiveRecord::Base) ? model.class : model
98
- end
99
-
100
- def current_shard
101
- Thread.current['octopus.current_shard'] ||= :master
102
- end
103
-
104
- def current_shard=(shard_symbol)
105
- self.current_slave_group = nil
106
- if shard_symbol.is_a?(Array)
107
- shard_symbol.each { |symbol| fail "Nonexistent Shard Name: #{symbol}" if @shards[symbol].nil? }
108
- elsif shard_symbol.is_a?(Hash)
109
- hash = shard_symbol
110
- shard_symbol = hash[:shard]
111
- slave_group_symbol = hash[:slave_group]
112
-
113
- if shard_symbol.nil? && slave_group_symbol.nil?
114
- fail 'Neither shard or slave group must be specified'
115
- end
116
-
117
- if shard_symbol.present?
118
- fail "Nonexistent Shard Name: #{shard_symbol}" if @shards[shard_symbol].nil?
119
- end
120
-
121
- if slave_group_symbol.present?
122
- if (@shards_slave_groups.try(:[], shard_symbol).present? && @shards_slave_groups[shard_symbol][slave_group_symbol].nil?) ||
123
- (@shards_slave_groups.try(:[], shard_symbol).nil? && @slave_groups[slave_group_symbol].nil?)
124
- fail "Nonexistent Slave Group Name: #{slave_group_symbol} in shards config: #{@shards_config.inspect}"
125
- end
126
- self.current_slave_group = slave_group_symbol
127
- end
128
- else
129
- fail "Nonexistent Shard Name: #{shard_symbol}" if @shards[shard_symbol].nil?
130
- end
131
-
132
- Thread.current['octopus.current_shard'] = shard_symbol
36
+ def execute(sql, name = nil)
37
+ conn = select_connection
38
+ clean_connection_proxy if should_clean_connection_proxy?('execute')
39
+ conn.execute(sql, name)
133
40
  end
134
41
 
135
- def current_group
136
- Thread.current['octopus.current_group']
42
+ def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
43
+ conn = select_connection
44
+ clean_connection_proxy if should_clean_connection_proxy?('insert')
45
+ conn.insert(arel, name, pk, id_value, sequence_name, binds)
137
46
  end
138
47
 
139
- def current_group=(group_symbol)
140
- # TODO: Error message should include all groups if given more than one bad name.
141
- [group_symbol].flatten.compact.each do |group|
142
- fail "Nonexistent Group Name: #{group}" unless has_group?(group)
143
- end
144
-
145
- Thread.current['octopus.current_group'] = group_symbol
48
+ def update(arel, name = nil, binds = [])
49
+ conn = select_connection
50
+ # Call the legacy should_clean_connection_proxy? method here, emulating an insert.
51
+ clean_connection_proxy if should_clean_connection_proxy?('insert')
52
+ conn.update(arel, name, binds)
146
53
  end
147
54
 
148
- def current_slave_group
149
- Thread.current['octopus.current_slave_group']
55
+ def delete(*args, &block)
56
+ legacy_method_missing_logic('delete', *args, &block)
150
57
  end
151
58
 
152
- def current_slave_group=(slave_group_symbol)
153
- Thread.current['octopus.current_slave_group'] = slave_group_symbol
59
+ def select_all(*args, &block)
60
+ legacy_method_missing_logic('select_all', *args, &block)
154
61
  end
155
62
 
156
- def block
157
- Thread.current['octopus.block']
158
- end
159
-
160
- def block=(block)
161
- Thread.current['octopus.block'] = block
162
- end
163
-
164
- def last_current_shard
165
- Thread.current['octopus.last_current_shard']
166
- end
167
-
168
- def last_current_shard=(last_current_shard)
169
- Thread.current['octopus.last_current_shard'] = last_current_shard
170
- end
171
-
172
- def fully_replicated?
173
- @fully_replicated || Thread.current['octopus.fully_replicated']
174
- end
175
-
176
- # Public: Whether or not a group exists with the given name converted to a
177
- # string.
178
- #
179
- # Returns a boolean.
180
- def has_group?(group)
181
- @groups.key?(group.to_s)
182
- end
183
-
184
- # Public: Retrieves names of all loaded shards.
185
- #
186
- # Returns an array of shard names as symbols
187
- def shard_names
188
- @shards.keys
189
- end
190
-
191
- # Public: Retrieves the defined shards for a given group.
192
- #
193
- # Returns an array of shard names as symbols or nil if the group is not
194
- # defined.
195
- def shards_for_group(group)
196
- @groups.fetch(group.to_s, nil)
63
+ def select_value(*args, &block)
64
+ legacy_method_missing_logic('select_value', *args, &block)
197
65
  end
198
66
 
199
67
  # Rails 3.1 sets automatic_reconnect to false when it removes
@@ -202,22 +70,14 @@ module Octopus
202
70
  # reconnect, but in Rails 3.1 the flag prevents this.
203
71
  def safe_connection(connection_pool)
204
72
  connection_pool.automatic_reconnect ||= true
205
- if !connection_pool.connected? && @shards[:master].connection.query_cache_enabled
73
+ if !connection_pool.connected? && shards[Octopus.master_shard].connection.query_cache_enabled
206
74
  connection_pool.connection.enable_query_cache!
207
75
  end
208
76
  connection_pool.connection
209
77
  end
210
78
 
211
79
  def select_connection
212
- safe_connection(@shards[shard_name])
213
- end
214
-
215
- def shard_name
216
- current_shard.is_a?(Array) ? current_shard.first : current_shard
217
- end
218
-
219
- def should_clean_table_name?
220
- @adapters.size > 1
80
+ safe_connection(shards[shard_name])
221
81
  end
222
82
 
223
83
  def run_queries_on_shard(shard, &_block)
@@ -229,13 +89,23 @@ module Octopus
229
89
  end
230
90
 
231
91
  def send_queries_to_multiple_shards(shards, &block)
232
- shards.each do |shard|
92
+ shards.map do |shard|
233
93
  run_queries_on_shard(shard, &block)
234
94
  end
235
95
  end
236
96
 
97
+ def send_queries_to_group(group, &block)
98
+ using_group(group) do
99
+ send_queries_to_multiple_shards(shards_for_group(group), &block)
100
+ end
101
+ end
102
+
103
+ def send_queries_to_all_shards(&block)
104
+ send_queries_to_multiple_shards(shard_names.uniq { |shard_name| shards[shard_name] }, &block)
105
+ end
106
+
237
107
  def clean_connection_proxy
238
- self.current_shard = :master
108
+ self.current_shard = Octopus.master_shard
239
109
  self.current_model = nil
240
110
  self.current_group = nil
241
111
  self.block = nil
@@ -249,7 +119,7 @@ module Octopus
249
119
 
250
120
  def transaction(options = {}, &block)
251
121
  if !sharded && current_model_replicated?
252
- run_queries_on_shard(:master) do
122
+ run_queries_on_shard(Octopus.master_shard) do
253
123
  select_connection.transaction(options, &block)
254
124
  end
255
125
  else
@@ -258,20 +128,7 @@ module Octopus
258
128
  end
259
129
 
260
130
  def method_missing(method, *args, &block)
261
- if should_clean_connection_proxy?(method)
262
- conn = select_connection
263
- self.last_current_shard = current_shard
264
- clean_connection_proxy
265
- conn.send(method, *args, &block)
266
- elsif should_send_queries_to_shard_slave_group?(method)
267
- send_queries_to_shard_slave_group(method, *args, &block)
268
- elsif should_send_queries_to_slave_group?(method)
269
- send_queries_to_slave_group(method, *args, &block)
270
- elsif should_send_queries_to_replicated_databases?(method)
271
- send_queries_to_selected_slave(method, *args, &block)
272
- else
273
- select_connection.send(method, *args, &block)
274
- end
131
+ legacy_method_missing_logic(method, *args, &block)
275
132
  end
276
133
 
277
134
  def respond_to?(method, include_private = false)
@@ -279,16 +136,18 @@ module Octopus
279
136
  end
280
137
 
281
138
  def connection_pool
282
- @shards[current_shard]
139
+ shards[current_shard]
283
140
  end
284
141
 
285
- def enable_query_cache!
286
- clear_query_cache
287
- with_each_healthy_shard { |v| v.connected? && safe_connection(v).enable_query_cache! }
288
- end
142
+ if Octopus.rails4?
143
+ def enable_query_cache!
144
+ clear_query_cache
145
+ with_each_healthy_shard { |v| v.connected? && safe_connection(v).enable_query_cache! }
146
+ end
289
147
 
290
- def disable_query_cache!
291
- with_each_healthy_shard { |v| v.connected? && safe_connection(v).disable_query_cache! }
148
+ def disable_query_cache!
149
+ with_each_healthy_shard { |v| v.connected? && safe_connection(v).disable_query_cache! }
150
+ end
292
151
  end
293
152
 
294
153
  def clear_query_cache
@@ -301,33 +160,81 @@ module Octopus
301
160
 
302
161
  def clear_all_connections!
303
162
  with_each_healthy_shard(&:disconnect!)
163
+
164
+ if Octopus.atleast_rails52?
165
+ # On Rails 5.2 it is no longer safe to re-use connection pools after they have been discarded
166
+ # This happens on webservers with forking, for example Phusion Passenger.
167
+ # Therefor after we clear all connections we reinitialize the shards to get fresh and not discarded ConnectionPool objects
168
+ proxy_config.reinitialize_shards
169
+ end
304
170
  end
305
171
 
306
172
  def connected?
307
- @shards.any? { |_k, v| v.connected? }
173
+ shards.any? { |_k, v| v.connected? }
308
174
  end
309
175
 
310
176
  def should_send_queries_to_shard_slave_group?(method)
311
- should_use_slaves_for_method?(method) && @shards_slave_groups.try(:[], current_shard).try(:[], current_slave_group).present?
177
+ should_use_slaves_for_method?(method) && shards_slave_groups.try(:[], current_shard).try(:[], current_slave_group).present?
312
178
  end
313
179
 
314
180
  def send_queries_to_shard_slave_group(method, *args, &block)
315
- send_queries_to_balancer(@shards_slave_groups[current_shard][current_slave_group], method, *args, &block)
181
+ send_queries_to_balancer(shards_slave_groups[current_shard][current_slave_group], method, *args, &block)
316
182
  end
317
183
 
318
184
  def should_send_queries_to_slave_group?(method)
319
- should_use_slaves_for_method?(method) && @slave_groups.try(:[], current_slave_group).present?
185
+ should_use_slaves_for_method?(method) && slave_groups.try(:[], current_slave_group).present?
320
186
  end
321
187
 
322
188
  def send_queries_to_slave_group(method, *args, &block)
323
- send_queries_to_balancer(@slave_groups[current_slave_group], method, *args, &block)
189
+ send_queries_to_balancer(slave_groups[current_slave_group], method, *args, &block)
190
+ end
191
+
192
+ def current_model_replicated?
193
+ replicated && (current_model.try(:replicated) || fully_replicated?)
194
+ end
195
+
196
+ def initialize_schema_migrations_table
197
+ if Octopus.atleast_rails52?
198
+ select_connection.transaction { ActiveRecord::SchemaMigration.create_table }
199
+ else
200
+ select_connection.initialize_schema_migrations_table
201
+ end
202
+ end
203
+
204
+ def initialize_metadata_table
205
+ select_connection.transaction { ActiveRecord::InternalMetadata.create_table }
324
206
  end
325
207
 
326
208
  protected
327
209
 
210
+ # @thiagopradi - This legacy method missing logic will be keep for a while for compatibility
211
+ # and will be removed when Octopus 1.0 will be released.
212
+ # We are planning to migrate to a much stable logic for the Proxy that doesn't require method missing.
213
+ def legacy_method_missing_logic(method, *args, &block)
214
+ if should_clean_connection_proxy?(method)
215
+ conn = select_connection
216
+ clean_connection_proxy
217
+ conn.send(method, *args, &block)
218
+ elsif should_send_queries_to_shard_slave_group?(method)
219
+ send_queries_to_shard_slave_group(method, *args, &block)
220
+ elsif should_send_queries_to_slave_group?(method)
221
+ send_queries_to_slave_group(method, *args, &block)
222
+ elsif should_send_queries_to_replicated_databases?(method)
223
+ send_queries_to_selected_slave(method, *args, &block)
224
+ else
225
+ val = select_connection.send(method, *args, &block)
226
+
227
+ if val.instance_of? ActiveRecord::Result
228
+ val.current_shard = shard_name
229
+ end
230
+
231
+ val
232
+ end
233
+ end
234
+
328
235
  # Ensure that a single failing slave doesn't take down the entire application
329
236
  def with_each_healthy_shard
330
- @shards.each do |shard_name, v|
237
+ shards.each do |shard_name, v|
331
238
  begin
332
239
  yield(v)
333
240
  rescue => e
@@ -339,17 +246,10 @@ module Octopus
339
246
  end
340
247
  end
341
248
 
342
- conn_handler = ActiveRecord::Base.connection_handler
343
- if conn_handler.respond_to?(:connection_pool_list)
344
- # Rails 4+
345
- ar_pools = conn_handler.connection_pool_list
346
- else
347
- # Rails 3.2
348
- ar_pools = conn_handler.connection_pools.values
349
- end
249
+ ar_pools = ActiveRecord::Base.connection_handler.connection_pool_list
350
250
 
351
251
  ar_pools.each do |pool|
352
- next if pool == @shards[:master] # Already handled this
252
+ next if pool == shards[:master] # Already handled this
353
253
 
354
254
  begin
355
255
  yield(pool)
@@ -363,57 +263,20 @@ module Octopus
363
263
  end
364
264
  end
365
265
 
366
- def connection_pool_for(adapter, config)
367
- if Octopus.rails4?
368
- arg = ActiveRecord::ConnectionAdapters::ConnectionSpecification.new(adapter.dup, config)
369
- else
370
- arg = ActiveRecord::Base::ConnectionSpecification.new(adapter.dup, config)
371
- end
372
-
373
- ActiveRecord::ConnectionAdapters::ConnectionPool.new(arg)
374
- end
375
-
376
- def initialize_adapter(adapter)
377
- @adapters << adapter
378
- begin
379
- require "active_record/connection_adapters/#{adapter}_adapter"
380
- rescue LoadError
381
- raise "Please install the #{adapter} adapter: `gem install activerecord-#{adapter}-adapter` (#{$ERROR_INFO})"
382
- end
383
- end
384
-
385
- def resolve_string_connection(spec)
386
- if Octopus.rails41?
387
- resolver = ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver.new({})
388
- HashWithIndifferentAccess.new(resolver.spec(spec).config)
389
- else
390
- if Octopus.rails4?
391
- resolver = ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver.new(spec, {})
392
- else
393
- resolver = ActiveRecord::Base::ConnectionSpecification::Resolver.new(spec, {})
394
- end
395
- HashWithIndifferentAccess.new(resolver.spec.config)
396
- end
397
- end
398
-
399
266
  def should_clean_connection_proxy?(method)
400
267
  method.to_s =~ /insert|select|execute/ && !current_model_replicated? && (!block || block != current_shard)
401
268
  end
402
269
 
403
270
  # Try to use slaves if and only if `replicated: true` is specified in `shards.yml` and no slaves groups are defined
404
271
  def should_send_queries_to_replicated_databases?(method)
405
- @replicated && method.to_s =~ /select/ && !block && !slaves_grouped?
406
- end
407
-
408
- def current_model_replicated?
409
- @replicated && (current_model.try(:replicated) || fully_replicated?)
272
+ replicated && method.to_s =~ /select/ && !block && !slaves_grouped?
410
273
  end
411
274
 
412
275
  def send_queries_to_selected_slave(method, *args, &block)
413
276
  if current_model.replicated || fully_replicated?
414
- selected_slave = @slaves_load_balancer.next
277
+ selected_slave = slaves_load_balancer.next current_load_balance_options
415
278
  else
416
- selected_slave = :master
279
+ selected_slave = Octopus.master_shard
417
280
  end
418
281
 
419
282
  send_queries_to_slave(selected_slave, method, *args, &block)
@@ -433,20 +296,24 @@ module Octopus
433
296
  end
434
297
 
435
298
  def slaves_grouped?
436
- @slave_groups.present?
299
+ slave_groups.present?
437
300
  end
438
301
 
439
302
  # Temporarily switch `current_shard` to the next slave in a slave group and send queries to it
440
303
  # while preserving `current_shard`
441
304
  def send_queries_to_balancer(balancer, method, *args, &block)
442
- send_queries_to_slave(balancer.next, method, *args, &block)
305
+ send_queries_to_slave(balancer.next(current_load_balance_options), method, *args, &block)
443
306
  end
444
307
 
445
308
  # Temporarily switch `current_shard` to the specified slave and send queries to it
446
309
  # while preserving `current_shard`
447
310
  def send_queries_to_slave(slave, method, *args, &block)
448
311
  using_shard(slave) do
449
- select_connection.send(method, *args, &block)
312
+ val = select_connection.send(method, *args, &block)
313
+ if val.instance_of? ActiveRecord::Result
314
+ val.current_shard = slave
315
+ end
316
+ val
450
317
  end
451
318
  end
452
319
 
@@ -468,6 +335,8 @@ module Octopus
468
335
  # Temporarily switch `current_shard` and run the block
469
336
  def using_shard(shard, &_block)
470
337
  older_shard = current_shard
338
+ older_slave_group = current_slave_group
339
+ older_load_balance_options = current_load_balance_options
471
340
 
472
341
  begin
473
342
  unless current_model && !current_model.allowed_shard?(shard)
@@ -476,15 +345,21 @@ module Octopus
476
345
  yield
477
346
  ensure
478
347
  self.current_shard = older_shard
348
+ self.current_slave_group = older_slave_group
349
+ self.current_load_balance_options = older_load_balance_options
479
350
  end
480
351
  end
481
352
 
482
- def structurally_slave?(config)
483
- config.is_a?(Hash) && config.key?('adapter')
484
- end
353
+ # Temporarily switch `current_group` and run the block
354
+ def using_group(group, &_block)
355
+ older_group = current_group
485
356
 
486
- def structurally_slave_group?(config)
487
- config.is_a?(Hash) && config.values.any? { |v| structurally_slave? v }
357
+ begin
358
+ self.current_group = group
359
+ yield
360
+ ensure
361
+ self.current_group = older_group
362
+ end
488
363
  end
489
364
  end
490
365
  end