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