ar-octopus 0.8.5 → 0.10.2

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 (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