activerecord-turntable 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +25 -0
- data/.rspec +3 -0
- data/Gemfile +25 -0
- data/Guardfile +9 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +290 -0
- data/Rakefile +101 -0
- data/activerecord-turntable.gemspec +47 -0
- data/lib/active_record/turntable.rb +58 -0
- data/lib/active_record/turntable/active_record_ext.rb +26 -0
- data/lib/active_record/turntable/active_record_ext/.gitkeep +0 -0
- data/lib/active_record/turntable/active_record_ext/abstract_adapter.rb +50 -0
- data/lib/active_record/turntable/active_record_ext/clever_load.rb +90 -0
- data/lib/active_record/turntable/active_record_ext/fixtures.rb +131 -0
- data/lib/active_record/turntable/active_record_ext/log_subscriber.rb +64 -0
- data/lib/active_record/turntable/active_record_ext/persistence.rb +95 -0
- data/lib/active_record/turntable/active_record_ext/schema_dumper.rb +107 -0
- data/lib/active_record/turntable/active_record_ext/sequencer.rb +28 -0
- data/lib/active_record/turntable/active_record_ext/transactions.rb +33 -0
- data/lib/active_record/turntable/algorithm.rb +7 -0
- data/lib/active_record/turntable/algorithm/.gitkeep +0 -0
- data/lib/active_record/turntable/algorithm/base.rb +11 -0
- data/lib/active_record/turntable/algorithm/range_algorithm.rb +37 -0
- data/lib/active_record/turntable/algorithm/range_bsearch_algorithm.rb +41 -0
- data/lib/active_record/turntable/base.rb +130 -0
- data/lib/active_record/turntable/cluster.rb +70 -0
- data/lib/active_record/turntable/compatible.rb +19 -0
- data/lib/active_record/turntable/config.rb +24 -0
- data/lib/active_record/turntable/connection_proxy.rb +218 -0
- data/lib/active_record/turntable/connection_proxy/mixable.rb +39 -0
- data/lib/active_record/turntable/error.rb +8 -0
- data/lib/active_record/turntable/helpers.rb +5 -0
- data/lib/active_record/turntable/helpers/test_helper.rb +25 -0
- data/lib/active_record/turntable/master_shard.rb +28 -0
- data/lib/active_record/turntable/migration.rb +132 -0
- data/lib/active_record/turntable/mixer.rb +203 -0
- data/lib/active_record/turntable/mixer/fader.rb +34 -0
- data/lib/active_record/turntable/mixer/fader/calculate_shards_sum_result.rb +15 -0
- data/lib/active_record/turntable/mixer/fader/insert_shards_merge_result.rb +24 -0
- data/lib/active_record/turntable/mixer/fader/select_shards_merge_result.rb +22 -0
- data/lib/active_record/turntable/mixer/fader/specified_shard.rb +12 -0
- data/lib/active_record/turntable/mixer/fader/update_shards_merge_result.rb +17 -0
- data/lib/active_record/turntable/pool_proxy.rb +56 -0
- data/lib/active_record/turntable/rack.rb +5 -0
- data/lib/active_record/turntable/rack/connection_management.rb +18 -0
- data/lib/active_record/turntable/railtie.rb +14 -0
- data/lib/active_record/turntable/railties/databases.rake +205 -0
- data/lib/active_record/turntable/seq_shard.rb +14 -0
- data/lib/active_record/turntable/sequencer.rb +46 -0
- data/lib/active_record/turntable/sequencer/api.rb +36 -0
- data/lib/active_record/turntable/sequencer/mysql.rb +32 -0
- data/lib/active_record/turntable/shard.rb +48 -0
- data/lib/active_record/turntable/sql_tree_patch.rb +199 -0
- data/lib/active_record/turntable/version.rb +5 -0
- data/lib/activerecord-turntable.rb +2 -0
- data/lib/generators/active_record/turntable/install_generator.rb +14 -0
- data/lib/generators/templates/turntable.yml +40 -0
- data/sample_app/.gitignore +16 -0
- data/sample_app/Gemfile +41 -0
- data/sample_app/README.rdoc +261 -0
- data/sample_app/Rakefile +7 -0
- data/sample_app/app/assets/images/rails.png +0 -0
- data/sample_app/app/assets/javascripts/application.js +15 -0
- data/sample_app/app/assets/stylesheets/application.css +13 -0
- data/sample_app/app/controllers/application_controller.rb +3 -0
- data/sample_app/app/helpers/application_helper.rb +2 -0
- data/sample_app/app/mailers/.gitkeep +0 -0
- data/sample_app/app/models/.gitkeep +0 -0
- data/sample_app/app/models/user.rb +4 -0
- data/sample_app/app/views/layouts/application.html.erb +14 -0
- data/sample_app/config.ru +4 -0
- data/sample_app/config/application.rb +65 -0
- data/sample_app/config/boot.rb +6 -0
- data/sample_app/config/database.yml +70 -0
- data/sample_app/config/environment.rb +5 -0
- data/sample_app/config/environments/development.rb +37 -0
- data/sample_app/config/environments/production.rb +67 -0
- data/sample_app/config/environments/test.rb +37 -0
- data/sample_app/config/initializers/backtrace_silencers.rb +7 -0
- data/sample_app/config/initializers/inflections.rb +15 -0
- data/sample_app/config/initializers/mime_types.rb +5 -0
- data/sample_app/config/initializers/secret_token.rb +7 -0
- data/sample_app/config/initializers/session_store.rb +8 -0
- data/sample_app/config/initializers/wrap_parameters.rb +14 -0
- data/sample_app/config/locales/en.yml +5 -0
- data/sample_app/config/routes.rb +58 -0
- data/sample_app/config/turntable.yml +64 -0
- data/sample_app/db/migrate/20120316073058_create_users.rb +11 -0
- data/sample_app/db/seeds.rb +7 -0
- data/sample_app/lib/assets/.gitkeep +0 -0
- data/sample_app/lib/tasks/.gitkeep +0 -0
- data/sample_app/log/.gitkeep +0 -0
- data/sample_app/public/404.html +26 -0
- data/sample_app/public/422.html +26 -0
- data/sample_app/public/500.html +25 -0
- data/sample_app/public/favicon.ico +0 -0
- data/sample_app/public/index.html +241 -0
- data/sample_app/public/robots.txt +5 -0
- data/sample_app/script/rails +6 -0
- data/sample_app/vendor/assets/javascripts/.gitkeep +0 -0
- data/sample_app/vendor/assets/stylesheets/.gitkeep +0 -0
- data/sample_app/vendor/plugins/.gitkeep +0 -0
- data/script/performance/algorithm +32 -0
- data/spec/active_record/turntable/active_record_ext/clever_load_spec.rb +81 -0
- data/spec/active_record/turntable/active_record_ext/persistence_spec.rb +151 -0
- data/spec/active_record/turntable/algorithm/range_algorithm_spec.rb +35 -0
- data/spec/active_record/turntable/algorithm_spec.rb +69 -0
- data/spec/active_record/turntable/base_spec.rb +13 -0
- data/spec/active_record/turntable/cluster_spec.rb +18 -0
- data/spec/active_record/turntable/config_spec.rb +17 -0
- data/spec/active_record/turntable/connection_proxy_spec.rb +186 -0
- data/spec/active_record/turntable/finder_spec.rb +27 -0
- data/spec/active_record/turntable/mixer/fader_spec.rb +4 -0
- data/spec/active_record/turntable/mixer_spec.rb +114 -0
- data/spec/active_record/turntable/shard_spec.rb +21 -0
- data/spec/active_record/turntable_spec.rb +30 -0
- data/spec/config/database.yml +45 -0
- data/spec/config/turntable.yml +17 -0
- data/spec/fabricators/.gitkeep +0 -0
- data/spec/fabricators/turntable_fabricator.rb +14 -0
- data/spec/migrations/.gitkeep +0 -0
- data/spec/migrations/001_create_users.rb +16 -0
- data/spec/migrations/002_create_user_statuses.rb +16 -0
- data/spec/migrations/003_create_cards.rb +14 -0
- data/spec/migrations/004_create_cards_users.rb +15 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/test_models.rb +27 -0
- data/spec/turntable_helper.rb +29 -0
- metadata +367 -0
@@ -0,0 +1,132 @@
|
|
1
|
+
module ActiveRecord::Turntable::Migration
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
# AR < 3.1
|
5
|
+
def self.extended(base)
|
6
|
+
class << base
|
7
|
+
def announce_with_turntable(message)
|
8
|
+
announce_without_turntable("#{message} - #{get_current_shard}")
|
9
|
+
end
|
10
|
+
|
11
|
+
alias_method_chain :migrate, :turntable
|
12
|
+
alias_method_chain :announce, :turntable
|
13
|
+
include ShardDefinition
|
14
|
+
end
|
15
|
+
base.class_eval do
|
16
|
+
class_inheritable_accessor :target_shards
|
17
|
+
end
|
18
|
+
::ActiveRecord::ConnectionAdapters::AbstractAdapter.send(:include, SchemaStatementsExt)
|
19
|
+
end
|
20
|
+
|
21
|
+
# AR >= 3.1
|
22
|
+
included do
|
23
|
+
extend ShardDefinition
|
24
|
+
class_attribute :target_shards
|
25
|
+
def announce_with_turntable(message)
|
26
|
+
announce_without_turntable("#{message} - #{get_current_shard}")
|
27
|
+
end
|
28
|
+
|
29
|
+
alias_method_chain :migrate, :turntable
|
30
|
+
alias_method_chain :announce, :turntable
|
31
|
+
::ActiveRecord::ConnectionAdapters::AbstractAdapter.send(:include, SchemaStatementsExt)
|
32
|
+
::ActiveRecord::Migration::CommandRecorder.send(:include, CommandRecorder)
|
33
|
+
end
|
34
|
+
|
35
|
+
# for all
|
36
|
+
module ShardDefinition
|
37
|
+
def clusters(*cluster_names)
|
38
|
+
config = ActiveRecord::Base.turntable_config
|
39
|
+
(self.target_shards ||= []) <<
|
40
|
+
if cluster_names.first == :all
|
41
|
+
config['clusters'].map do |name, cluster_conf|
|
42
|
+
cluster_conf["shards"].map {|shard| shard["connection"]}
|
43
|
+
end
|
44
|
+
else
|
45
|
+
cluster_names.map do |cluster_name|
|
46
|
+
config['clusters'][cluster_name]["shards"].map do |shard|
|
47
|
+
shard["connection"]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def shards(*connection_names)
|
54
|
+
(self.target_shards ||= []) << connection_names
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def get_current_shard
|
59
|
+
"Shard: #{@@current_shard}" if @@current_shard
|
60
|
+
end
|
61
|
+
|
62
|
+
def migrate_with_turntable(direction)
|
63
|
+
config = ActiveRecord::Base.configurations
|
64
|
+
@@current_shard = nil
|
65
|
+
shards = (self.class.target_shards||=[]).flatten.uniq.compact
|
66
|
+
if self.class.target_shards.blank?
|
67
|
+
return migrate_without_turntable(direction)
|
68
|
+
end
|
69
|
+
|
70
|
+
shards_conf = shards.map do |shard|
|
71
|
+
config[Rails.env||"development"]["shards"][shard]
|
72
|
+
end
|
73
|
+
seqs = config[Rails.env||"development"]["seq"]
|
74
|
+
shards_conf += seqs.values
|
75
|
+
shards_conf << config[Rails.env||"development"]
|
76
|
+
shards_conf.each_with_index do |conf, idx|
|
77
|
+
@@current_shard = (shards[idx] || seqs.keys[idx - shards.size] || "master")
|
78
|
+
ActiveRecord::Base.establish_connection(conf)
|
79
|
+
if !ActiveRecord::Base.connection.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name())
|
80
|
+
ActiveRecord::Base.connection.initialize_schema_migrations_table
|
81
|
+
end
|
82
|
+
migrate_without_turntable(direction)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
module SchemaStatementsExt
|
87
|
+
def create_sequence_for(table_name, options = { })
|
88
|
+
# TODO: pkname should be pulled from table definitions
|
89
|
+
pkname = "id"
|
90
|
+
sequence_table_name = ActiveRecord::Turntable::Sequencer.sequence_name(table_name, "id")
|
91
|
+
create_table(sequence_table_name, options)
|
92
|
+
execute "ALTER TABLE #{quote_table_name(sequence_table_name)} MODIFY id bigint(20) DEFAULT NULL auto_increment NOT NULL;"
|
93
|
+
execute "INSERT INTO #{quote_table_name(sequence_table_name)} (`id`) VALUES (0)"
|
94
|
+
end
|
95
|
+
|
96
|
+
def drop_sequence_for(table_name, options = { })
|
97
|
+
# TODO: pkname should be pulled from table definitions
|
98
|
+
pkname = "id"
|
99
|
+
sequence_table_name = ActiveRecord::Turntable::Sequencer.sequence_name(table_name, "id")
|
100
|
+
drop_table(sequence_table_name)
|
101
|
+
end
|
102
|
+
|
103
|
+
def rename_sequence_for(table_name, new_name)
|
104
|
+
# TODO: pkname should pulled from table definitions
|
105
|
+
seq_table_name = ActiveRecord::Turntable::Sequencer.sequence_name(table_name, "id")
|
106
|
+
new_seq_name = ActiveRecord::Turntable::Sequencer.sequence_name(new_name, "id")
|
107
|
+
rename_table(seq_table_name, new_seq_name)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
module CommandRecorder
|
112
|
+
def create_sequence_for(*args)
|
113
|
+
record(:create_sequence_for, args)
|
114
|
+
end
|
115
|
+
|
116
|
+
def rename_sequence_for(*args)
|
117
|
+
record(:rename_sequence_for, args)
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
def invert_create_sequence_for(args)
|
123
|
+
[:drop_sequence_for, args]
|
124
|
+
end
|
125
|
+
|
126
|
+
def invert_rename_sequence_for(args)
|
127
|
+
[:rename_sequence_for, args.reverse]
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
@@ -0,0 +1,203 @@
|
|
1
|
+
require 'active_support/core_ext/object/try'
|
2
|
+
require 'active_record/turntable/sql_tree_patch'
|
3
|
+
|
4
|
+
module ActiveRecord::Turntable
|
5
|
+
class Mixer
|
6
|
+
autoload :Fader, "active_record/turntable/mixer/fader"
|
7
|
+
delegate :logger, :to => ActiveRecord::Base
|
8
|
+
|
9
|
+
NOT_USED_FOR_SHARDING_OPERATORS_REGEXP = /\A(NOT IN|IS|IS NOT|BETWEEN|LIKE|\!\=|<<|>>|<>|>\=|<=|[\*\+\-\/\%\|\&><])\z/
|
10
|
+
|
11
|
+
def initialize(proxy)
|
12
|
+
@proxy = proxy
|
13
|
+
end
|
14
|
+
|
15
|
+
def build_fader(method_name, query, *args, &block)
|
16
|
+
method = method_name.to_s
|
17
|
+
if @proxy.shard_fixed?
|
18
|
+
return SpecifiedShard.new(@proxy,
|
19
|
+
{ @proxy.fixed_shard => query },
|
20
|
+
method, query, *args, &block)
|
21
|
+
end
|
22
|
+
binds = (method == 'insert') ? args[4] : args[1]
|
23
|
+
binded_query = bind_sql(query, binds)
|
24
|
+
|
25
|
+
begin
|
26
|
+
tree = SQLTree[binded_query]
|
27
|
+
rescue Exception => err
|
28
|
+
logger.warn { "[ActiveRecord::Turntable][BUG] Error on Parsing SQL: #{binded_query}, on_method: #{method_name}" }
|
29
|
+
raise err
|
30
|
+
end
|
31
|
+
|
32
|
+
case tree
|
33
|
+
when SQLTree::Node::SelectQuery
|
34
|
+
build_select_fader(tree, method, query, *args, &block)
|
35
|
+
when SQLTree::Node::UpdateQuery, SQLTree::Node::DeleteQuery
|
36
|
+
build_update_fader(tree, method, query, *args, &block)
|
37
|
+
when SQLTree::Node::InsertQuery
|
38
|
+
build_insert_fader(tree, method, query, *args, &block)
|
39
|
+
else
|
40
|
+
# send to master shard
|
41
|
+
Fader::SpecifiedShard.new(@proxy,
|
42
|
+
{ @proxy.master => query },
|
43
|
+
method, query, *args, &block)
|
44
|
+
end
|
45
|
+
rescue Exception => err
|
46
|
+
logger.warn { "[ActiveRecord::Turntable][BUG] Error on Building Fader: #{binded_query}, on_method: #{method_name}" }
|
47
|
+
raise err
|
48
|
+
end
|
49
|
+
|
50
|
+
def find_shard_keys(tree, table_name, shard_key)
|
51
|
+
return [] unless tree.respond_to?(:operator)
|
52
|
+
|
53
|
+
case tree.operator
|
54
|
+
when "OR"
|
55
|
+
lkeys = find_shard_keys(tree.lhs, table_name, shard_key)
|
56
|
+
rkeys = find_shard_keys(tree.rhs, table_name, shard_key)
|
57
|
+
if lkeys.present? and rkeys.present?
|
58
|
+
lkeys + rkeys
|
59
|
+
else
|
60
|
+
[]
|
61
|
+
end
|
62
|
+
when "AND"
|
63
|
+
lkeys = find_shard_keys(tree.lhs, table_name, shard_key)
|
64
|
+
rkeys = find_shard_keys(tree.rhs, table_name, shard_key)
|
65
|
+
if lkeys.present? or rkeys.present?
|
66
|
+
lkeys + rkeys
|
67
|
+
else
|
68
|
+
[]
|
69
|
+
end
|
70
|
+
when "IN", "=", "=="
|
71
|
+
field = tree.lhs.respond_to?(:table) ? tree.lhs : nil
|
72
|
+
if tree.rhs.is_a?(SQLTree::Node::SubQuery)
|
73
|
+
if field.try(:table) == table_name and field.name == shard_key
|
74
|
+
find_shard_keys(tree.rhs.where, table_name, shard_key)
|
75
|
+
else
|
76
|
+
[]
|
77
|
+
end
|
78
|
+
else
|
79
|
+
values = Array(tree.rhs)
|
80
|
+
if field.try(:table) == table_name and field.name == shard_key and
|
81
|
+
!tree.rhs.is_a?(SQLTree::Node::SubQuery)
|
82
|
+
values.map(&:value).compact
|
83
|
+
else
|
84
|
+
[]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
when NOT_USED_FOR_SHARDING_OPERATORS_REGEXP
|
88
|
+
[]
|
89
|
+
else
|
90
|
+
raise ActiveRecord::Turntable::UnknownOperatorError,
|
91
|
+
"[ActiveRecord::Turntable][BUG] Found Unknown SQL Operator:'#{tree.operator if tree.respond_to?(:operaor)}', Please report this bug."
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def divide_insert_values(tree, shard_key_name)
|
98
|
+
idx = tree.fields.find_index {|f| f.name == shard_key_name.to_s }
|
99
|
+
result = {}
|
100
|
+
tree.values.each do |val|
|
101
|
+
(result[val[idx].value] ||= []) << val
|
102
|
+
end
|
103
|
+
return result
|
104
|
+
end
|
105
|
+
|
106
|
+
def build_shards_with_same_query(shards, query)
|
107
|
+
Hash[shards.map {|s| [s, query] }]
|
108
|
+
end
|
109
|
+
|
110
|
+
if ActiveRecord::VERSION::STRING < '3.1'
|
111
|
+
def bind_sql(sql, binds)
|
112
|
+
sql
|
113
|
+
end
|
114
|
+
else
|
115
|
+
def bind_sql(sql, binds)
|
116
|
+
# TODO: substitution value should be determined by adapter
|
117
|
+
query = sql.is_a?(String) ? sql : @proxy.to_sql(sql, binds ? binds.dup : [])
|
118
|
+
query = if query.include?("\0") and binds.is_a?(Array) and binds[0].is_a?(Array) and binds[0][0].is_a?(ActiveRecord::ConnectionAdapters::Column)
|
119
|
+
binds = binds.dup
|
120
|
+
query.gsub("\0") { @proxy.master.connection.quote(*binds.shift.reverse) }
|
121
|
+
else
|
122
|
+
query
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def build_select_fader(tree, method, query, *args, &block)
|
128
|
+
shard_keys = if !tree.where and tree.from.size == 1 and SQLTree::Node::SubQuery === tree.from.first.table_reference.table
|
129
|
+
find_shard_keys(tree.from.first.table_reference.table.where,
|
130
|
+
@proxy.cluster.klass.table_name,
|
131
|
+
@proxy.cluster.klass.turntable_shard_key.to_s)
|
132
|
+
else
|
133
|
+
find_shard_keys(tree.where,
|
134
|
+
@proxy.cluster.klass.table_name,
|
135
|
+
@proxy.cluster.klass.turntable_shard_key.to_s)
|
136
|
+
end
|
137
|
+
|
138
|
+
if shard_keys.size == 1 # shard
|
139
|
+
return Fader::SpecifiedShard.new(@proxy,
|
140
|
+
{ @proxy.cluster.select_shard(shard_keys.first) => query },
|
141
|
+
method, query, *args, &block)
|
142
|
+
elsif tree.group_by or tree.order_by or tree.limit.try(:value).to_i > 1
|
143
|
+
raise CannotSpecifyShardError, "cannot specify shard for query: #{binded_query}"
|
144
|
+
elsif shard_keys.present?
|
145
|
+
if SQLTree::Node::SelectDeclaration === tree.select.first and
|
146
|
+
SQLTree::Node::CountAggregrate === tree.select.first.expression
|
147
|
+
return Fader::CalculateShardsSumResult.new(@proxy,
|
148
|
+
build_shards_with_same_query(@proxy.shards.values, query),
|
149
|
+
method, query, *args, &block)
|
150
|
+
else
|
151
|
+
return Fader::SelectShardsMergeResult.new(@proxy,
|
152
|
+
Hash[shard_keys.map {|k| [@proxy.cluster.select_shard(k), query] }],
|
153
|
+
method, query, *args, &block
|
154
|
+
)
|
155
|
+
end
|
156
|
+
else # scan all shards
|
157
|
+
if SQLTree::Node::SelectDeclaration === tree.select.first and
|
158
|
+
SQLTree::Node::CountAggregrate === tree.select.first.expression
|
159
|
+
return Fader::CalculateShardsSumResult.new(@proxy,
|
160
|
+
build_shards_with_same_query(@proxy.shards.values, query),
|
161
|
+
method, query, *args, &block)
|
162
|
+
elsif SQLTree::Node::AllFieldsDeclaration === tree.select.first or
|
163
|
+
SQLTree::Node::Expression::Value === tree.select.first.expression or
|
164
|
+
SQLTree::Node::Expression::Variable === tree.select.first.expression
|
165
|
+
|
166
|
+
return Fader::SelectShardsMergeResult.new(@proxy,
|
167
|
+
build_shards_with_same_query(@proxy.shards.values, query),
|
168
|
+
method, query, *args, &block
|
169
|
+
)
|
170
|
+
else
|
171
|
+
raise CannotSpecifyShardError, "cannot specify shard for query: #{binded_query}"
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def build_update_fader(tree, method, query, *args, &block)
|
177
|
+
shard_keys = find_shard_keys(tree.where, @proxy.cluster.klass.table_name, @proxy.cluster.klass.turntable_shard_key.to_s)
|
178
|
+
shards_with_query = if shard_keys.present?
|
179
|
+
build_shards_with_same_query(shard_keys.map {|k| @proxy.cluster.select_shard(k) }, query)
|
180
|
+
else
|
181
|
+
build_shards_with_same_query(@proxy.shards.values, query)
|
182
|
+
end
|
183
|
+
Fader::UpdateShardsMergeResult.new(@proxy,
|
184
|
+
shards_with_query,
|
185
|
+
method, query, *args, &block)
|
186
|
+
end
|
187
|
+
|
188
|
+
def build_insert_fader(tree, method, query, *args, &block)
|
189
|
+
values_hash = divide_insert_values(tree, @proxy.cluster.klass.turntable_shard_key)
|
190
|
+
shards_with_query = {}
|
191
|
+
values_hash.each do |k,vs|
|
192
|
+
tree.values = [[SQLTree::Node::Expression::Variable.new("\\0")]]
|
193
|
+
sql = tree.to_sql
|
194
|
+
value_sql = vs.map do |val|
|
195
|
+
"(#{val.map { |v| @proxy.connection.quote(v.value)}.join(', ')})"
|
196
|
+
end.join(', ')
|
197
|
+
sql.gsub!('("\0")') { value_sql }
|
198
|
+
shards_with_query[@proxy.cluster.select_shard(k)] = sql
|
199
|
+
end
|
200
|
+
Fader::InsertShardsMergeResult.new(@proxy, shards_with_query, method, query, *args, &block)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
module ActiveRecord::Turntable
|
3
|
+
class Mixer
|
4
|
+
class Fader
|
5
|
+
# 単数shard
|
6
|
+
autoload :SpecifiedShard, "active_record/turntable/mixer/fader/specified_shard"
|
7
|
+
|
8
|
+
# 複数shard
|
9
|
+
autoload :SelectShardsMergeResult, "active_record/turntable/mixer/fader/select_shards_merge_result"
|
10
|
+
autoload :InsertShardsMergeResult, "active_record/turntable/mixer/fader/insert_shards_merge_result"
|
11
|
+
autoload :UpdateShardsMergeResult, "active_record/turntable/mixer/fader/update_shards_merge_result"
|
12
|
+
|
13
|
+
# count
|
14
|
+
autoload :CalculateShardsSumResult, "active_record/turntable/mixer/fader/calculate_shards_sum_result"
|
15
|
+
|
16
|
+
attr_reader :shards_query_hash
|
17
|
+
attr_reader :called_method
|
18
|
+
attr_reader :query
|
19
|
+
|
20
|
+
def initialize(proxy, shards_query_hash, called_method, query, *args, &block)
|
21
|
+
@proxy = proxy
|
22
|
+
@shards_query_hash = shards_query_hash
|
23
|
+
@called_method = called_method
|
24
|
+
@query = query
|
25
|
+
@args = args
|
26
|
+
@block = block
|
27
|
+
end
|
28
|
+
|
29
|
+
def execute
|
30
|
+
raise ActiveRecord::Turntable::NotImplementedError, "Called abstract method"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module ActiveRecord::Turntable
|
2
|
+
class Mixer
|
3
|
+
class Fader
|
4
|
+
class CalculateShardsSumResult < Fader
|
5
|
+
def execute
|
6
|
+
@shards_query_hash.map do |shard, query|
|
7
|
+
args = @args.dup
|
8
|
+
args[1] = args[1].dup if args[1].present?
|
9
|
+
shard.connection.send(@called_method, query, *@args, &@block)
|
10
|
+
end.inject(&:+)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module ActiveRecord::Turntable
|
2
|
+
class Mixer
|
3
|
+
class Fader
|
4
|
+
class InsertShardsMergeResult < Fader
|
5
|
+
def execute
|
6
|
+
if @shards_query_hash.size == 1
|
7
|
+
@proxy.shards_transaction(@shards_query_hash.keys) do
|
8
|
+
shard, query = @shards_query_hash.first
|
9
|
+
shard.connection.send(@called_method, query, *@args, &@block)
|
10
|
+
end
|
11
|
+
else
|
12
|
+
@proxy.shards_transaction(@shards_query_hash.keys) do
|
13
|
+
@shards_query_hash.each do |shard, query|
|
14
|
+
args = @args.dup
|
15
|
+
args[4] = args[4].dup if args[4].present?
|
16
|
+
shard.connection.send(@called_method, query, *args, &@block)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module ActiveRecord::Turntable
|
2
|
+
class Mixer
|
3
|
+
class Fader
|
4
|
+
class SelectShardsMergeResult < Fader
|
5
|
+
def execute
|
6
|
+
res = @shards_query_hash.map do |shard, query|
|
7
|
+
args = @args.dup
|
8
|
+
args[1] = args[1].dup if args[1].present?
|
9
|
+
shard.connection.send(@called_method, query, *args, &@block)
|
10
|
+
end.flatten(1).compact
|
11
|
+
|
12
|
+
case @called_method
|
13
|
+
when "select_value", "select_one"
|
14
|
+
res.first if res
|
15
|
+
else
|
16
|
+
res
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|