activerecord-turntable 1.0.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.
Files changed (130) hide show
  1. data/.document +5 -0
  2. data/.gitignore +25 -0
  3. data/.rspec +3 -0
  4. data/Gemfile +25 -0
  5. data/Guardfile +9 -0
  6. data/LICENSE.txt +20 -0
  7. data/README.rdoc +290 -0
  8. data/Rakefile +101 -0
  9. data/activerecord-turntable.gemspec +47 -0
  10. data/lib/active_record/turntable.rb +58 -0
  11. data/lib/active_record/turntable/active_record_ext.rb +26 -0
  12. data/lib/active_record/turntable/active_record_ext/.gitkeep +0 -0
  13. data/lib/active_record/turntable/active_record_ext/abstract_adapter.rb +50 -0
  14. data/lib/active_record/turntable/active_record_ext/clever_load.rb +90 -0
  15. data/lib/active_record/turntable/active_record_ext/fixtures.rb +131 -0
  16. data/lib/active_record/turntable/active_record_ext/log_subscriber.rb +64 -0
  17. data/lib/active_record/turntable/active_record_ext/persistence.rb +95 -0
  18. data/lib/active_record/turntable/active_record_ext/schema_dumper.rb +107 -0
  19. data/lib/active_record/turntable/active_record_ext/sequencer.rb +28 -0
  20. data/lib/active_record/turntable/active_record_ext/transactions.rb +33 -0
  21. data/lib/active_record/turntable/algorithm.rb +7 -0
  22. data/lib/active_record/turntable/algorithm/.gitkeep +0 -0
  23. data/lib/active_record/turntable/algorithm/base.rb +11 -0
  24. data/lib/active_record/turntable/algorithm/range_algorithm.rb +37 -0
  25. data/lib/active_record/turntable/algorithm/range_bsearch_algorithm.rb +41 -0
  26. data/lib/active_record/turntable/base.rb +130 -0
  27. data/lib/active_record/turntable/cluster.rb +70 -0
  28. data/lib/active_record/turntable/compatible.rb +19 -0
  29. data/lib/active_record/turntable/config.rb +24 -0
  30. data/lib/active_record/turntable/connection_proxy.rb +218 -0
  31. data/lib/active_record/turntable/connection_proxy/mixable.rb +39 -0
  32. data/lib/active_record/turntable/error.rb +8 -0
  33. data/lib/active_record/turntable/helpers.rb +5 -0
  34. data/lib/active_record/turntable/helpers/test_helper.rb +25 -0
  35. data/lib/active_record/turntable/master_shard.rb +28 -0
  36. data/lib/active_record/turntable/migration.rb +132 -0
  37. data/lib/active_record/turntable/mixer.rb +203 -0
  38. data/lib/active_record/turntable/mixer/fader.rb +34 -0
  39. data/lib/active_record/turntable/mixer/fader/calculate_shards_sum_result.rb +15 -0
  40. data/lib/active_record/turntable/mixer/fader/insert_shards_merge_result.rb +24 -0
  41. data/lib/active_record/turntable/mixer/fader/select_shards_merge_result.rb +22 -0
  42. data/lib/active_record/turntable/mixer/fader/specified_shard.rb +12 -0
  43. data/lib/active_record/turntable/mixer/fader/update_shards_merge_result.rb +17 -0
  44. data/lib/active_record/turntable/pool_proxy.rb +56 -0
  45. data/lib/active_record/turntable/rack.rb +5 -0
  46. data/lib/active_record/turntable/rack/connection_management.rb +18 -0
  47. data/lib/active_record/turntable/railtie.rb +14 -0
  48. data/lib/active_record/turntable/railties/databases.rake +205 -0
  49. data/lib/active_record/turntable/seq_shard.rb +14 -0
  50. data/lib/active_record/turntable/sequencer.rb +46 -0
  51. data/lib/active_record/turntable/sequencer/api.rb +36 -0
  52. data/lib/active_record/turntable/sequencer/mysql.rb +32 -0
  53. data/lib/active_record/turntable/shard.rb +48 -0
  54. data/lib/active_record/turntable/sql_tree_patch.rb +199 -0
  55. data/lib/active_record/turntable/version.rb +5 -0
  56. data/lib/activerecord-turntable.rb +2 -0
  57. data/lib/generators/active_record/turntable/install_generator.rb +14 -0
  58. data/lib/generators/templates/turntable.yml +40 -0
  59. data/sample_app/.gitignore +16 -0
  60. data/sample_app/Gemfile +41 -0
  61. data/sample_app/README.rdoc +261 -0
  62. data/sample_app/Rakefile +7 -0
  63. data/sample_app/app/assets/images/rails.png +0 -0
  64. data/sample_app/app/assets/javascripts/application.js +15 -0
  65. data/sample_app/app/assets/stylesheets/application.css +13 -0
  66. data/sample_app/app/controllers/application_controller.rb +3 -0
  67. data/sample_app/app/helpers/application_helper.rb +2 -0
  68. data/sample_app/app/mailers/.gitkeep +0 -0
  69. data/sample_app/app/models/.gitkeep +0 -0
  70. data/sample_app/app/models/user.rb +4 -0
  71. data/sample_app/app/views/layouts/application.html.erb +14 -0
  72. data/sample_app/config.ru +4 -0
  73. data/sample_app/config/application.rb +65 -0
  74. data/sample_app/config/boot.rb +6 -0
  75. data/sample_app/config/database.yml +70 -0
  76. data/sample_app/config/environment.rb +5 -0
  77. data/sample_app/config/environments/development.rb +37 -0
  78. data/sample_app/config/environments/production.rb +67 -0
  79. data/sample_app/config/environments/test.rb +37 -0
  80. data/sample_app/config/initializers/backtrace_silencers.rb +7 -0
  81. data/sample_app/config/initializers/inflections.rb +15 -0
  82. data/sample_app/config/initializers/mime_types.rb +5 -0
  83. data/sample_app/config/initializers/secret_token.rb +7 -0
  84. data/sample_app/config/initializers/session_store.rb +8 -0
  85. data/sample_app/config/initializers/wrap_parameters.rb +14 -0
  86. data/sample_app/config/locales/en.yml +5 -0
  87. data/sample_app/config/routes.rb +58 -0
  88. data/sample_app/config/turntable.yml +64 -0
  89. data/sample_app/db/migrate/20120316073058_create_users.rb +11 -0
  90. data/sample_app/db/seeds.rb +7 -0
  91. data/sample_app/lib/assets/.gitkeep +0 -0
  92. data/sample_app/lib/tasks/.gitkeep +0 -0
  93. data/sample_app/log/.gitkeep +0 -0
  94. data/sample_app/public/404.html +26 -0
  95. data/sample_app/public/422.html +26 -0
  96. data/sample_app/public/500.html +25 -0
  97. data/sample_app/public/favicon.ico +0 -0
  98. data/sample_app/public/index.html +241 -0
  99. data/sample_app/public/robots.txt +5 -0
  100. data/sample_app/script/rails +6 -0
  101. data/sample_app/vendor/assets/javascripts/.gitkeep +0 -0
  102. data/sample_app/vendor/assets/stylesheets/.gitkeep +0 -0
  103. data/sample_app/vendor/plugins/.gitkeep +0 -0
  104. data/script/performance/algorithm +32 -0
  105. data/spec/active_record/turntable/active_record_ext/clever_load_spec.rb +81 -0
  106. data/spec/active_record/turntable/active_record_ext/persistence_spec.rb +151 -0
  107. data/spec/active_record/turntable/algorithm/range_algorithm_spec.rb +35 -0
  108. data/spec/active_record/turntable/algorithm_spec.rb +69 -0
  109. data/spec/active_record/turntable/base_spec.rb +13 -0
  110. data/spec/active_record/turntable/cluster_spec.rb +18 -0
  111. data/spec/active_record/turntable/config_spec.rb +17 -0
  112. data/spec/active_record/turntable/connection_proxy_spec.rb +186 -0
  113. data/spec/active_record/turntable/finder_spec.rb +27 -0
  114. data/spec/active_record/turntable/mixer/fader_spec.rb +4 -0
  115. data/spec/active_record/turntable/mixer_spec.rb +114 -0
  116. data/spec/active_record/turntable/shard_spec.rb +21 -0
  117. data/spec/active_record/turntable_spec.rb +30 -0
  118. data/spec/config/database.yml +45 -0
  119. data/spec/config/turntable.yml +17 -0
  120. data/spec/fabricators/.gitkeep +0 -0
  121. data/spec/fabricators/turntable_fabricator.rb +14 -0
  122. data/spec/migrations/.gitkeep +0 -0
  123. data/spec/migrations/001_create_users.rb +16 -0
  124. data/spec/migrations/002_create_user_statuses.rb +16 -0
  125. data/spec/migrations/003_create_cards.rb +14 -0
  126. data/spec/migrations/004_create_cards_users.rb +15 -0
  127. data/spec/spec_helper.rb +23 -0
  128. data/spec/test_models.rb +27 -0
  129. data/spec/turntable_helper.rb +29 -0
  130. metadata +367 -0
@@ -0,0 +1,95 @@
1
+ module ActiveRecord::Turntable::ActiveRecordExt
2
+ module Persistence
3
+ if ActiveRecord::VERSION::STRING < '3.1'
4
+ ::ActiveRecord::Persistence.class_eval do
5
+ def destroy
6
+ klass = self.class
7
+ if persisted?
8
+ condition_scope = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id))
9
+ if klass.turntable_enabled? and klass.primary_key != klass.turntable_shard_key.to_s
10
+ condition_scope = condition_scope.where(klass.turntable_shard_key => self.send(turntable_shard_key))
11
+ end
12
+ condition_scope.delete_all
13
+ end
14
+
15
+ @destroyed = true
16
+ freeze
17
+ end
18
+
19
+ private
20
+
21
+ # overrides ActiveRecord::Persistence's original method so that
22
+ def update(attribute_names = @attributes.keys)
23
+ klass = self.class
24
+ attributes_with_values = arel_attributes_values(false, false, attribute_names)
25
+ return 0 if attributes_with_values.empty?
26
+ condition_scope = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id))
27
+ if klass.turntable_enabled? and klass.primary_key != klass.turntable_shard_key.to_s
28
+ condition_scope = condition_scope.where(klass.turntable_shard_key => self.send(turntable_shard_key))
29
+ end
30
+ condition_scope.arel.update(attributes_with_values)
31
+ end
32
+ end
33
+
34
+ else
35
+ ::ActiveRecord::Persistence.class_eval do
36
+ def destroy
37
+ klass = self.class
38
+ destroy_associations
39
+
40
+ if persisted?
41
+ ActiveRecord::IdentityMap.remove(self) if ActiveRecord::IdentityMap.enabled?
42
+ pk = klass.primary_key
43
+ column = klass.columns_hash[pk]
44
+ substitute = connection.substitute_at(column, 0)
45
+
46
+ relation = klass.unscoped.where(klass.arel_table[pk].eq(substitute))
47
+ if klass.turntable_enabled? and klass.primary_key != klass.turntable_shard_key.to_s
48
+ relation = relation.where(klass.turntable_shard_key => self.send(turntable_shard_key))
49
+ end
50
+ relation.bind_values = [[column, id]]
51
+ relation.delete_all
52
+ end
53
+
54
+ @destroyed = true
55
+ freeze
56
+ end
57
+
58
+ def destroy_without_callbacks
59
+ klass = self.class
60
+ destroy_associations
61
+
62
+ if persisted?
63
+ ActiveRecord::IdentityMap.remove(self) if ActiveRecord::IdentityMap.enabled?
64
+ pk = klass.primary_key
65
+ column = klass.columns_hash[pk]
66
+ substitute = connection.substitute_at(column, 0)
67
+
68
+ relation = klass.unscoped.where(klass.arel_table[pk].eq(substitute))
69
+ if klass.turntable_enabled? and klass.primary_key != klass.turntable_shard_key.to_s
70
+ relation = relation.where(klass.turntable_shard_key => self.send(turntable_shard_key))
71
+ end
72
+ relation.bind_values = [[column, id]]
73
+ relation.delete_all
74
+ end
75
+
76
+ @destroyed = true
77
+ freeze
78
+ end
79
+
80
+ private
81
+ def update(attribute_names = @attributes.keys)
82
+ attributes_with_values = arel_attributes_values(false, false, attribute_names)
83
+ return 0 if attributes_with_values.empty?
84
+ klass = self.class
85
+ condition_scope = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id))
86
+ if klass.turntable_enabled? and klass.primary_key != klass.turntable_shard_key.to_s
87
+ condition_scope = condition_scope.where(klass.turntable_shard_key => self.send(turntable_shard_key))
88
+ end
89
+ stmt = condition_scope.arel.compile_update(attributes_with_values)
90
+ klass.connection.update stmt.to_sql
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,107 @@
1
+ # -*- coding: utf-8 -*-
2
+ module ActiveRecord::Turntable
3
+ module ActiveRecordExt
4
+ module SchemaDumper
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ alias_method_chain :table, :turntable
9
+ end
10
+
11
+ private
12
+
13
+ def table_with_turntable(table, stream)
14
+ columns = @connection.columns(table)
15
+ begin
16
+ tbl = StringIO.new
17
+
18
+ # first dump primary key column
19
+ if @connection.respond_to?(:pk_and_sequence_for)
20
+ pk, _ = @connection.pk_and_sequence_for(table)
21
+ elsif @connection.respond_to?(:primary_key)
22
+ pk = @connection.primary_key(table)
23
+ end
24
+
25
+ # turntable sequencer dump
26
+ if table =~ /\A(.*)_id_seq\z/
27
+ tbl.print " create_sequence_for #{$1.inspect}"
28
+ else
29
+ tbl.print " create_table #{table.inspect}"
30
+ end
31
+
32
+ if columns.detect { |c| c.name == pk }
33
+ if pk != 'id'
34
+ tbl.print %Q(, :primary_key => "#{pk}")
35
+ end
36
+ else
37
+ tbl.print ", :id => false"
38
+ end
39
+ tbl.print ", :force => true"
40
+ tbl.puts " do |t|"
41
+
42
+ # then dump all non-primary key columns
43
+ column_specs = columns.map do |column|
44
+ raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" if @types[column.type].nil?
45
+ next if column.name == pk
46
+ spec = {}
47
+ spec[:name] = column.name.inspect
48
+
49
+ # AR has an optimisation which handles zero-scale decimals as integers. This
50
+ # code ensures that the dumper still dumps the column as a decimal.
51
+ spec[:type] = if column.type == :integer && [/^numeric/, /^decimal/].any? { |e| e.match(column.sql_type) }
52
+ 'decimal'
53
+ else
54
+ column.type.to_s
55
+ end
56
+ spec[:limit] = column.limit.inspect if column.limit != @types[column.type][:limit] && spec[:type] != 'decimal'
57
+ spec[:precision] = column.precision.inspect if column.precision
58
+ spec[:scale] = column.scale.inspect if column.scale
59
+ spec[:null] = 'false' unless column.null
60
+ spec[:default] = default_string(column.default) if column.has_default?
61
+ (spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k.inspect} => ")}
62
+ spec
63
+ end.compact
64
+
65
+ # find all migration keys used in this table
66
+ keys = [:name, :limit, :precision, :scale, :default, :null] & column_specs.map{ |k| k.keys }.flatten
67
+
68
+ # figure out the lengths for each column based on above keys
69
+ lengths = keys.map{ |key| column_specs.map{ |spec| spec[key] ? spec[key].length + 2 : 0 }.max }
70
+
71
+ # the string we're going to sprintf our values against, with standardized column widths
72
+ format_string = lengths.map{ |len| "%-#{len}s" }
73
+
74
+ # find the max length for the 'type' column, which is special
75
+ type_length = column_specs.map{ |column| column[:type].length }.max
76
+
77
+ # add column type definition to our format string
78
+ format_string.unshift " t.%-#{type_length}s "
79
+
80
+ format_string *= ''
81
+
82
+ column_specs.each do |colspec|
83
+ values = keys.zip(lengths).map{ |key, len| colspec.key?(key) ? colspec[key] + ", " : " " * len }
84
+ values.unshift colspec[:type]
85
+ tbl.print((format_string % values).gsub(/,\s*$/, ''))
86
+ tbl.puts
87
+ end
88
+
89
+ tbl.puts " end"
90
+ tbl.puts
91
+
92
+ indexes(table, tbl)
93
+
94
+ tbl.rewind
95
+ stream.print tbl.read
96
+ rescue => e
97
+ stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
98
+ stream.puts "# #{e.message}"
99
+ stream.puts
100
+ end
101
+
102
+ stream
103
+ end
104
+
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,28 @@
1
+ module ActiveRecord::Turntable::ActiveRecordExt
2
+ module Sequencer
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ include DatabaseStatements
7
+ alias_method_chain :prefetch_primary_key?, :turntable
8
+ end
9
+
10
+ module DatabaseStatements
11
+ def default_sequence_name(table_name, pk = nil)
12
+ ActiveRecord::Turntable::Sequencer.sequence_name(table_name, pk)
13
+ end
14
+ end
15
+
16
+ def prefetch_primary_key_with_turntable?(table_name = nil)
17
+ ActiveRecord::Turntable::Sequencer.has_sequencer?(table_name)
18
+ end
19
+
20
+ def next_sequence_value(sequence_name)
21
+ ActiveRecord::Turntable::Sequencer.sequences[sequence_name].next_sequence_value(sequence_name)
22
+ end
23
+
24
+ def current_sequence_value(sequence_name)
25
+ ActiveRecord::Turntable::Sequencer.sequences[sequence_name].current_sequence_value(sequence_name)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,33 @@
1
+ module ActiveRecord::Turntable
2
+ module ActiveRecordExt
3
+ module Transactions
4
+ def with_transaction_returning_status
5
+ if self.class.turntable_enabled?
6
+ status = nil
7
+ if self.new_record? and self.turntable_shard_key.to_s == self.class.primary_key and
8
+ self.id.nil? and connection.prefetch_primary_key?(self.class.table_name)
9
+ self.id = connection.next_sequence_value(self.class.sequence_name)
10
+ end
11
+ self.class.connection.shards_transaction([self.turntable_shard]) do
12
+ add_to_transaction
13
+ status = yield
14
+ raise ActiveRecord::Rollback unless status
15
+ end
16
+ status
17
+ else
18
+ super
19
+ end
20
+ end
21
+
22
+ def add_to_transaction
23
+ if self.class.turntable_enabled?
24
+ if self.turntable_shard.connection.add_transaction_record(self)
25
+ remember_transaction_record_state
26
+ end
27
+ else
28
+ super
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,7 @@
1
+ module ActiveRecord::Turntable
2
+ module Algorithm
3
+ autoload :Base, "active_record/turntable/algorithm/base"
4
+ autoload :RangeAlgorithm, "active_record/turntable/algorithm/range_algorithm"
5
+ autoload :RangeBsearchAlgorithm, "active_record/turntable/algorithm/range_bsearch_algorithm"
6
+ end
7
+ end
File without changes
@@ -0,0 +1,11 @@
1
+ module ActiveRecord::Turntable::Algorithm
2
+ class Base
3
+ def initialize(config)
4
+ @config = config
5
+ end
6
+
7
+ def calculate(key)
8
+ raise ActiveRecord::Turntable::NotImplementedError, "not implemented"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,37 @@
1
+ # -*- coding: utf-8 -*-
2
+ module ActiveRecord::Turntable::Algorithm
3
+ class RangeAlgorithm < Base
4
+ def initialize(config)
5
+ @config = config
6
+ end
7
+
8
+ def calculate(key)
9
+ idx = calculate_idx(key)
10
+ @config["shards"][idx]["connection"]
11
+ rescue
12
+ raise ActiveRecord::Turntable::CannotSpecifyShardError, "cannot specify shard for key:#{key}"
13
+ end
14
+
15
+ def calculate_idx(key)
16
+ @config["shards"].find_index {|h| h["less_than"] > key }
17
+ end
18
+
19
+ # { connection_name => weight, ... }
20
+ def calculate_used_shards_with_weight(sequence_value)
21
+ idx = calculate_idx(sequence_value)
22
+ last_connection = calculate(sequence_value)
23
+ shards = @config["shards"][0..idx]
24
+ weighted_hash = Hash.new {|h,k| h[k]=0}
25
+ prev_max = 0
26
+ shards.each_with_index do |h,idx|
27
+ weighted_hash[h["connection"]] += if idx < shards.size - 1
28
+ h["less_than"] - prev_max - 1
29
+ else
30
+ sequence_value - prev_max
31
+ end
32
+ prev_max = h["less_than"] - 1
33
+ end
34
+ return weighted_hash
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,41 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'bsearch'
3
+ module ActiveRecord::Turntable::Algorithm
4
+ class RangeBsearchAlgorithm < Base
5
+ def initialize(config)
6
+ @config = config
7
+ @config["shards"].sort_by! {|a| a["less_than"]}
8
+ end
9
+
10
+ def calculate(key)
11
+ idx = calculate_idx(key)
12
+ @config["shards"][idx]["connection"]
13
+ rescue
14
+ raise ActiveRecord::Turntable::CannotSpecifyShardError, "cannot specify shard for key:#{key}"
15
+ end
16
+
17
+ def calculate_idx(key)
18
+ @config["shards"].bsearch_lower_boundary { |h|
19
+ h["less_than"] <=> key
20
+ }
21
+ end
22
+
23
+ # { connection_name => weight, ... }
24
+ def calculate_used_shards_with_weight(sequence_value)
25
+ idx = calculate_idx(sequence_value)
26
+ last_connection = calculate(sequence_value)
27
+ shards = @config["shards"][0..idx]
28
+ weighted_hash = Hash.new {|h,k| h[k]=0}
29
+ prev_max = 0
30
+ shards.each_with_index do |h,idx|
31
+ weighted_hash[h["connection"]] += if idx < shards.size - 1
32
+ h["less_than"] - prev_max - 1
33
+ else
34
+ sequence_value - prev_max
35
+ end
36
+ prev_max = h["less_than"] - 1
37
+ end
38
+ return weighted_hash
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,130 @@
1
+ module ActiveRecord::Turntable
2
+ module Base
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ include Compatible
7
+ class_attribute :turntable_connections,
8
+ :turntable_enabled, :turntable_sequencer_enabled
9
+
10
+ self.turntable_connections = {}
11
+ self.turntable_enabled = false
12
+ self.turntable_sequencer_enabled = false
13
+ class << self
14
+ delegate :shards_transaction, :to => :connection
15
+ end
16
+ end
17
+
18
+ module ClassMethods
19
+ def turntable(cluster_name, shard_key_name, options = {})
20
+ class_attribute :turntable_shard_key,
21
+ :turntable_cluster, :turntable_cluster_name
22
+
23
+ self.turntable_enabled = true
24
+ self.turntable_cluster_name = cluster_name
25
+ self.turntable_shard_key = shard_key_name
26
+ self.turntable_cluster = Cluster.new(
27
+ self,
28
+ turntable_config[:clusters][cluster_name],
29
+ options
30
+ )
31
+ turntable_replace_connection_pool
32
+ end
33
+
34
+ def force_transaction_all_shards!(options={}, &block)
35
+ force_connect_all_shards!
36
+ shards = turntable_connections.values
37
+ shards += [ActiveRecord::Base.connection_pool]
38
+ recursive_transaction(shards, options, &block)
39
+ end
40
+
41
+ def recursive_transaction(pools, options, &block)
42
+ pool = pools.shift
43
+ if pools.present?
44
+ pool.connection.transaction(options) do
45
+ recursive_transaction(pools, options, &block)
46
+ end
47
+ else
48
+ pool.connection.transaction(options, &block)
49
+ end
50
+ end
51
+
52
+ def force_connect_all_shards!
53
+ conf = configurations[Rails.env]
54
+ shards = conf["shards"]
55
+ shards = shards.merge(conf["seq"]) if conf["seq"]
56
+ shards.each do |name, config|
57
+ turntable_connections[name] ||=
58
+ ActiveRecord::ConnectionAdapters::ConnectionPool.new(spec_for(config))
59
+ end
60
+ end
61
+
62
+ def turntable_replace_connection_pool
63
+ ch = connection_handler
64
+ cp = turntable_cluster.connection_proxy
65
+ if ActiveRecord::VERSION::STRING >= '3.2.0'
66
+ ch.connection_pools[cp.spec] = PoolProxy.new(cp)
67
+ ch.instance_variable_get(:@class_to_pool)[name] = ch.connection_pools[cp.spec]
68
+ else
69
+ ch.connection_pools[name] = PoolProxy.new(cp)
70
+ end
71
+ end
72
+
73
+ def spec_for(config)
74
+ begin
75
+ require "active_record/connection_adapters/#{config['adapter']}_adapter"
76
+ rescue LoadError => e
77
+ raise "Please install the #{config['adapter']} adapter: `gem install activerecord-#{config['adapter']}-adapter` (#{e})"
78
+ end
79
+ adapter_method = "#{config['adapter']}_connection"
80
+ ActiveRecord::Base::ConnectionSpecification.new(config, adapter_method)
81
+ end
82
+
83
+ def clear_all_connections!
84
+ turntable_connections.values.each do |pool|
85
+ pool.disconnect!
86
+ end
87
+ end
88
+
89
+ def sequencer
90
+ class_attribute :turntable_sequencer
91
+ self.turntable_sequencer_enabled = true
92
+ self.turntable_sequencer = ActiveRecord::Turntable::Sequencer.build(self)
93
+ end
94
+
95
+ def turntable_enabled?
96
+ turntable_enabled
97
+ end
98
+
99
+ def sequencer_enabled?
100
+ turntable_sequencer_enabled
101
+ end
102
+
103
+ def current_sequence
104
+ connection.current_sequence_value(self.sequence_name) if sequencer_enabled?
105
+ end
106
+
107
+ def current_last_shard
108
+ turntable_cluster.select_shard(current_sequence) if sequencer_enabled?
109
+ end
110
+
111
+ def weighted_random_shard_with(*klasses, &block)
112
+ shards_weight = self.turntable_cluster.weighted_shards
113
+ sum = shards_weight.values.inject(&:+)
114
+ idx = rand(sum)
115
+ shard, weight = shards_weight.find {|k,v|
116
+ (idx -= v) < 0
117
+ }
118
+ self.connection.with_recursive_shards(shard.name, *klasses, &block)
119
+ end
120
+ end
121
+
122
+ def shards_transaction(options = {}, &block)
123
+ self.class.shards_transaction(options, &block)
124
+ end
125
+
126
+ def turntable_shard
127
+ turntable_cluster.select_shard(self.send(turntable_shard_key))
128
+ end
129
+ end
130
+ end