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,186 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveRecord::Turntable::ConnectionProxy do
4
+ before(:all) do
5
+ reload_turntable!(File.join(File.dirname(__FILE__), "../../config/turntable.yml"))
6
+ end
7
+
8
+ context "When initialized" do
9
+ before do
10
+ establish_connection_to("test")
11
+ truncate_shard
12
+ end
13
+ let(:cluster) { ActiveRecord::Turntable::Cluster.new(User, ActiveRecord::Base.turntable_config[:clusters][:user_cluster]) }
14
+ subject { ActiveRecord::Turntable::ConnectionProxy.new(cluster) }
15
+ its(:master_connection) { should == ActiveRecord::Base.connection }
16
+ end
17
+
18
+ context "AR3.1" do
19
+ it "should proxies columns" do
20
+ pending "spec not implemented yet"
21
+ end
22
+
23
+ it "should proxies columns_hash" do
24
+ pending "spec not implemented yet"
25
+ end
26
+ end
27
+
28
+ context "User insert with id" do
29
+ before do
30
+ establish_connection_to("test")
31
+ truncate_shard
32
+ ActiveRecord::Base.logger = Logger.new(STDOUT)
33
+ end
34
+
35
+ it "should be saved to user_shard_1 with id = 1" do
36
+ user = User.new
37
+ user.id = 1
38
+ # mock(User.turntable_cluster).select_shard(1) { User.turntable_cluster.shards[:user_shard_1] }
39
+ lambda {
40
+ user.save!
41
+ }.should_not raise_error
42
+ end
43
+
44
+ it "should be saved to user_shard_2 with id = 30000" do
45
+ user = User.new
46
+ user.id = 30000
47
+ # mock(User.turntable_cluster).select_shard(30000) { User.turntable_cluster.shards[:user_shard_2] }
48
+ lambda {
49
+ user.save!
50
+ }.should_not raise_error
51
+ end
52
+
53
+ it "should be saved to user_shard_2 with id = 30000 with SQL injection attack" do
54
+ user = User.new
55
+ user.id = 30000
56
+ user.nickname = "hogehgoge'00"
57
+ # mock(User.turntable_cluster).select_shard(30000) { User.turntable_cluster.shards[:user_shard_2] }
58
+ lambda {
59
+ user.save!
60
+ }.should_not raise_error
61
+ user.reload
62
+
63
+ end
64
+
65
+ it "should should be saved the same string when includes escaped string" do
66
+ user = User.new
67
+ user.id = 30000
68
+ user.nickname = "hoge@\n@\\@@\\nhoge\\\nhoge\\n"
69
+ user.save!
70
+ user.reload
71
+ user.nickname.should == "hoge@\n@\\@@\\nhoge\\\nhoge\\n"
72
+ end
73
+ end
74
+
75
+ context "When have 2 Users in different shards" do
76
+ before do
77
+ establish_connection_to("test")
78
+ truncate_shard
79
+ @user1 = User.new
80
+ @user1.id = 1
81
+ @user1.save!
82
+ @user2 = User.new
83
+ @user2.id = 30000
84
+ @user2.save!
85
+ end
86
+
87
+ it "should be saved to user_shard_1 with id = 1" do
88
+ @user1.nickname = "foobar"
89
+ lambda {
90
+ @user1.save!
91
+ }.should_not raise_error
92
+
93
+ end
94
+
95
+ it "should be saved to user_shard_2 with id = 30000" do
96
+ @user2.nickname = "hogehoge"
97
+ lambda {
98
+ @user2.save!
99
+ }.should_not raise_error
100
+ end
101
+
102
+ it "User.where('id IN (1, 30000)') returns 2 record" do
103
+ User.where(:id => [1, 30000]).all.size.should == 2
104
+ end
105
+
106
+ it "count should be 2" do
107
+ User.count.should == 2
108
+ end
109
+
110
+ it "User.all returns 2 User object" do
111
+ User.all.size.should == 2
112
+ end
113
+ end
114
+
115
+ context "When calling with_all" do
116
+ before do
117
+ establish_connection_to("test")
118
+ truncate_shard
119
+ @user1 = User.new
120
+ @user1.id = 1
121
+ @user1.nickname = 'user1'
122
+ @user1.save!
123
+ @user2 = User.new
124
+ @user2.id = 30000
125
+ @user2.nickname = 'user2'
126
+ @user2.save!
127
+ end
128
+
129
+ context "do; User.count; end" do
130
+ subject {
131
+ User.connection.with_all do
132
+ User.count
133
+ end
134
+ }
135
+ it { should have(3).items }
136
+
137
+ it "returns User.count of each shards" do
138
+ subject[0].should == 1
139
+ subject[1].should == 1
140
+ subject[2].should == 0
141
+ end
142
+ end
143
+
144
+ context "call with true" do
145
+ context "block raises error" do
146
+ subject {
147
+ User.connection.with_all(true) do
148
+ raise "Unko Error"
149
+ end
150
+ }
151
+ it { lambda { subject }.should_not raise_error }
152
+ it { should have(3).items }
153
+ it "collection " do
154
+ subject.each do |s|
155
+ s.should be_instance_of(RuntimeError)
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
161
+
162
+ context "When calling exists? with shard_key" do
163
+ subject { User.exists?(id: 1) }
164
+ it { should be_true }
165
+ end
166
+
167
+ context "When calling exists? with non-existed shard_key" do
168
+ subject { User.exists?(id: 3) }
169
+ it { should be_false }
170
+ end
171
+
172
+ context "When calling exists? with non shard_key" do
173
+ subject { User.exists?(nickname: 'user2') }
174
+ it { should be_true }
175
+ end
176
+
177
+ context "When calling exists? with non-existed non shard_key" do
178
+ subject { User.exists?(nickname: 'user999') }
179
+ it { should be_false }
180
+ end
181
+
182
+ context "#table_exists?" do
183
+ subject { User.connection.table_exists?(:users) }
184
+ it { should be_true }
185
+ end
186
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe "ActiveRecord::FinderMethods" do
4
+ before(:all) do
5
+ reload_turntable!(File.join(File.dirname(__FILE__), "../../config/turntable.yml"))
6
+ end
7
+
8
+ context "User insert with id" do
9
+ before do
10
+ establish_connection_to("test")
11
+ truncate_shard
12
+ ActiveRecord::Base.logger = Logger.new(STDOUT)
13
+ @user = User.new
14
+ @user.id = 1
15
+ @user.save
16
+ end
17
+
18
+ it "#find(1) should be == user" do
19
+ User.find(1).should == @user
20
+ end
21
+
22
+ it "#find(2) should raise error" do
23
+ lambda { User.find(2) }.should raise_error
24
+ end
25
+ end
26
+ end
27
+
@@ -0,0 +1,4 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveRecord::Turntable::Mixer::Fader do
4
+ end
@@ -0,0 +1,114 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveRecord::Turntable::Mixer do
4
+ before(:all) do
5
+ reload_turntable!(File.join(File.dirname(__FILE__), "../../config/turntable.yml"))
6
+ end
7
+
8
+ before do
9
+ establish_connection_to("test")
10
+ truncate_shard
11
+ @cluster = ActiveRecord::Turntable::Cluster.new(User, ActiveRecord::Base.turntable_config[:clusters][:user_cluster])
12
+ @connection_proxy = ActiveRecord::Turntable::ConnectionProxy.new(@cluster)
13
+ end
14
+
15
+ context "When initialized" do
16
+ before do
17
+ @mixer = ActiveRecord::Turntable::Mixer.new(@connection_proxy)
18
+ end
19
+
20
+ context "For Insert SQL" do
21
+ context "When call divide_insert_values with Single INSERT and shard_key 'id'" do
22
+ subject {
23
+ tree = SQLTree["INSERT INTO `users` (id, hp, mp) VALUES (1, 10, 10)"]
24
+ @mixer.send(:divide_insert_values, tree, "id")
25
+ }
26
+
27
+ it { should be_instance_of(Hash) }
28
+ it { should have_key(1) }
29
+ it { [1].should have(1).item }
30
+ end
31
+
32
+ context "When call divide_insert_values with Bulk INSERT and shard_key 'id'" do
33
+ subject {
34
+ tree = SQLTree["INSERT INTO `users` (id, hp, mp) VALUES (1, 10, 10), (2,10,10), (3,10,10)"]
35
+ @mixer.send(:divide_insert_values, tree, "id")
36
+ }
37
+
38
+ it { should be_instance_of(Hash) }
39
+ it { should have_key(3) }
40
+ it { [1].should have(1).item }
41
+ it { [2].should have(1).item }
42
+ it { [3].should have(1).item }
43
+ end
44
+ end
45
+
46
+ context "For Update SQL" do
47
+ context "When call find_shard_keys with eql shardkey condition" do
48
+ subject {
49
+ tree = SQLTree["UPDATE `users` SET `users`.`hp` = 20 WHERE `users`.`id` = 1"]
50
+ @mixer.find_shard_keys(tree.where, "users", "id")
51
+ }
52
+
53
+ it { should be_instance_of Array }
54
+ it { should == [1] }
55
+ end
56
+ end
57
+
58
+ context "For Delete SQL" do
59
+ context "When call find_shard_keys with eql shardkey condition" do
60
+ subject {
61
+ tree = SQLTree["DELETE FROM `users` WHERE `users`.`id` = 1"]
62
+ @mixer.find_shard_keys(tree.where, "users", "id")
63
+ }
64
+
65
+ it { should be_instance_of Array }
66
+ it { should == [1] }
67
+ end
68
+ end
69
+
70
+ context "For Select SQL" do
71
+ context "When call find_shard_keys with eql shardkey condition" do
72
+ subject {
73
+ tree = SQLTree["SELECT * FROM `users` WHERE `users`.`id` = 1"]
74
+ @mixer.find_shard_keys(tree.where, "users", "id")
75
+ }
76
+
77
+ it { should be_instance_of Array }
78
+ it { should == [1] }
79
+ end
80
+
81
+ context "When call find_shard_keys with shardkey collection condition" do
82
+ subject {
83
+ tree = SQLTree["SELECT * FROM `users` WHERE `users`.`id` IN (1,2,3,4,5)"]
84
+ @mixer.find_shard_keys(tree.where, "users", "id")
85
+ }
86
+
87
+ it { should be_instance_of Array }
88
+ it { should == [1,2,3,4,5] }
89
+ end
90
+
91
+ context "When call find_shard_keys with not determine shardkey condition" do
92
+ subject {
93
+ tree = SQLTree["SELECT * FROM `users` WHERE `users`.`id` = 1 OR 1"]
94
+ @mixer.find_shard_keys(tree.where, "users", "id")
95
+ }
96
+
97
+ it { should be_instance_of Array }
98
+ it { should == [] }
99
+ end
100
+
101
+ context "When call find_shard_keys with except table definition SQL" do
102
+ subject {
103
+ tree = SQLTree["SELECT * FROM `users` WHERE id = 10"]
104
+ @mixer.find_shard_keys(tree.where, "users", "id")
105
+ }
106
+
107
+ it { should be_instance_of Array }
108
+ it { should == [] }
109
+ end
110
+ end
111
+
112
+ end
113
+
114
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveRecord::Turntable::Shard do
4
+ before(:all) do
5
+ reload_turntable!(File.join(File.dirname(__FILE__), "../../config/turntable.yml"))
6
+ end
7
+
8
+ context "When initialized" do
9
+ before do
10
+ establish_connection_to("test")
11
+ truncate_shard
12
+ end
13
+
14
+ subject {
15
+ ActiveRecord::Turntable::Shard.new(ActiveRecord::Base.turntable_config[:clusters][:user_cluster][:shards][0])
16
+ }
17
+ its(:name) { should == ActiveRecord::Base.turntable_config[:clusters][:user_cluster][:shards][0][:connection] }
18
+ its(:connection) { should be_instance_of(ActiveRecord::ConnectionAdapters::Mysql2Adapter) }
19
+ its(:connection_pool) { should be_instance_of(ActiveRecord::ConnectionAdapters::ConnectionPool) }
20
+ end
21
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveRecord::Turntable do
4
+ before(:all) do
5
+ ActiveRecord::Base.send(:include, ActiveRecord::Turntable)
6
+ end
7
+
8
+ context "#config_file" do
9
+ it "should return Rails.root/config/turntable.yml default" do
10
+ unless defined?(::Rails); class ::Rails; end; end
11
+ stub(Rails).root { "/path/to/rails_root" }
12
+ ActiveRecord::Base.turntable_config_file = nil
13
+ ActiveRecord::Base.turntable_config_file.should == "/path/to/rails_root/config/turntable.yml"
14
+ end
15
+ end
16
+
17
+ context "#config_file=" do
18
+ it "should set config_file" do
19
+ ActiveRecord::Base.send(:include, ActiveRecord::Turntable)
20
+ filename = "hogefuga"
21
+ ActiveRecord::Base.turntable_config_file = filename
22
+ ActiveRecord::Base.turntable_config_file.should == filename
23
+ end
24
+ end
25
+
26
+ context "#config" do
27
+ subject { ActiveRecord::Base.turntable_config }
28
+ it { should be_instance_of(ActiveRecord::Turntable::Config) }
29
+ end
30
+ end
@@ -0,0 +1,45 @@
1
+ test:
2
+ adapter: mysql2
3
+ username: root
4
+ password:
5
+ host: localhost
6
+ port: 3306
7
+ encoding: utf8
8
+ database: turntable_master_test
9
+ seq:
10
+ user_seq_1:
11
+ adapter: mysql2
12
+ username: root
13
+ password:
14
+ host: localhost
15
+ port: 3306
16
+ encoding: utf8
17
+ database: turntable_seq_test
18
+ # seq_type: api
19
+ api_host: localhost
20
+ api_port: 9292
21
+ shards:
22
+ user_shard_1:
23
+ adapter: mysql2
24
+ username: root
25
+ password:
26
+ host: localhost
27
+ port: 3306
28
+ encoding: utf8
29
+ database: turntable1_test
30
+ user_shard_2:
31
+ adapter: mysql2
32
+ username: root
33
+ password:
34
+ host: localhost
35
+ port: 3306
36
+ encoding: utf8
37
+ database: turntable2_test
38
+ user_shard_3:
39
+ adapter: mysql2
40
+ username: root
41
+ password:
42
+ host: localhost
43
+ port: 3306
44
+ encoding: utf8
45
+ database: turntable3_test
@@ -0,0 +1,17 @@
1
+ test:
2
+ clusters:
3
+ user_cluster:
4
+ algorithm: range_bsearch
5
+ seq:
6
+ connection: user_seq_1
7
+ shards:
8
+ - connection: user_shard_1
9
+ less_than: 20000
10
+ - connection: user_shard_2
11
+ less_than: 40000
12
+ - connection: user_shard_1
13
+ less_than: 60000
14
+ - connection: user_shard_2
15
+ less_than: 80000
16
+ - connection: user_shard_3
17
+ less_than: 10000000