rubyrep 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (140) hide show
  1. data/History.txt +4 -0
  2. data/License.txt +20 -0
  3. data/Manifest.txt +137 -0
  4. data/README.txt +37 -0
  5. data/Rakefile +30 -0
  6. data/bin/rubyrep +8 -0
  7. data/config/hoe.rb +72 -0
  8. data/config/mysql_config.rb +25 -0
  9. data/config/postgres_config.rb +21 -0
  10. data/config/proxied_test_config.rb +14 -0
  11. data/config/redmine_config.rb +17 -0
  12. data/config/rep_config.rb +20 -0
  13. data/config/requirements.rb +32 -0
  14. data/config/test_config.rb +20 -0
  15. data/lib/rubyrep/base_runner.rb +195 -0
  16. data/lib/rubyrep/command_runner.rb +144 -0
  17. data/lib/rubyrep/committers/buffered_committer.rb +140 -0
  18. data/lib/rubyrep/committers/committers.rb +146 -0
  19. data/lib/rubyrep/configuration.rb +240 -0
  20. data/lib/rubyrep/connection_extenders/connection_extenders.rb +133 -0
  21. data/lib/rubyrep/connection_extenders/jdbc_extender.rb +284 -0
  22. data/lib/rubyrep/connection_extenders/mysql_extender.rb +168 -0
  23. data/lib/rubyrep/connection_extenders/postgresql_extender.rb +261 -0
  24. data/lib/rubyrep/database_proxy.rb +52 -0
  25. data/lib/rubyrep/direct_table_scan.rb +75 -0
  26. data/lib/rubyrep/generate_runner.rb +105 -0
  27. data/lib/rubyrep/initializer.rb +39 -0
  28. data/lib/rubyrep/logged_change.rb +326 -0
  29. data/lib/rubyrep/proxied_table_scan.rb +171 -0
  30. data/lib/rubyrep/proxy_block_cursor.rb +145 -0
  31. data/lib/rubyrep/proxy_connection.rb +318 -0
  32. data/lib/rubyrep/proxy_cursor.rb +44 -0
  33. data/lib/rubyrep/proxy_row_cursor.rb +43 -0
  34. data/lib/rubyrep/proxy_runner.rb +89 -0
  35. data/lib/rubyrep/replication_difference.rb +91 -0
  36. data/lib/rubyrep/replication_extenders/mysql_replication.rb +271 -0
  37. data/lib/rubyrep/replication_extenders/postgresql_replication.rb +204 -0
  38. data/lib/rubyrep/replication_extenders/replication_extenders.rb +26 -0
  39. data/lib/rubyrep/replication_helper.rb +104 -0
  40. data/lib/rubyrep/replication_initializer.rb +307 -0
  41. data/lib/rubyrep/replication_run.rb +48 -0
  42. data/lib/rubyrep/replication_runner.rb +138 -0
  43. data/lib/rubyrep/replicators/replicators.rb +37 -0
  44. data/lib/rubyrep/replicators/two_way_replicator.rb +334 -0
  45. data/lib/rubyrep/scan_progress_printers/progress_bar.rb +65 -0
  46. data/lib/rubyrep/scan_progress_printers/scan_progress_printers.rb +65 -0
  47. data/lib/rubyrep/scan_report_printers/scan_detail_reporter.rb +111 -0
  48. data/lib/rubyrep/scan_report_printers/scan_report_printers.rb +67 -0
  49. data/lib/rubyrep/scan_report_printers/scan_summary_reporter.rb +75 -0
  50. data/lib/rubyrep/scan_runner.rb +25 -0
  51. data/lib/rubyrep/session.rb +177 -0
  52. data/lib/rubyrep/sync_helper.rb +111 -0
  53. data/lib/rubyrep/sync_runner.rb +31 -0
  54. data/lib/rubyrep/syncers/syncers.rb +112 -0
  55. data/lib/rubyrep/syncers/two_way_syncer.rb +174 -0
  56. data/lib/rubyrep/table_scan.rb +54 -0
  57. data/lib/rubyrep/table_scan_helper.rb +38 -0
  58. data/lib/rubyrep/table_sorter.rb +70 -0
  59. data/lib/rubyrep/table_spec_resolver.rb +136 -0
  60. data/lib/rubyrep/table_sync.rb +68 -0
  61. data/lib/rubyrep/trigger_mode_switcher.rb +63 -0
  62. data/lib/rubyrep/type_casting_cursor.rb +31 -0
  63. data/lib/rubyrep/uninstall_runner.rb +92 -0
  64. data/lib/rubyrep/version.rb +9 -0
  65. data/lib/rubyrep.rb +68 -0
  66. data/script/destroy +14 -0
  67. data/script/generate +14 -0
  68. data/script/txt2html +74 -0
  69. data/setup.rb +1585 -0
  70. data/sims/performance/big_rep_spec.rb +100 -0
  71. data/sims/performance/big_scan_spec.rb +57 -0
  72. data/sims/performance/big_sync_spec.rb +141 -0
  73. data/sims/performance/performance.rake +228 -0
  74. data/sims/sim_helper.rb +24 -0
  75. data/spec/base_runner_spec.rb +218 -0
  76. data/spec/buffered_committer_spec.rb +271 -0
  77. data/spec/command_runner_spec.rb +145 -0
  78. data/spec/committers_spec.rb +174 -0
  79. data/spec/configuration_spec.rb +198 -0
  80. data/spec/connection_extender_interface_spec.rb +138 -0
  81. data/spec/connection_extenders_registration_spec.rb +129 -0
  82. data/spec/database_proxy_spec.rb +48 -0
  83. data/spec/database_rake_spec.rb +40 -0
  84. data/spec/db_specific_connection_extenders_spec.rb +34 -0
  85. data/spec/db_specific_replication_extenders_spec.rb +38 -0
  86. data/spec/direct_table_scan_spec.rb +61 -0
  87. data/spec/generate_runner_spec.rb +84 -0
  88. data/spec/initializer_spec.rb +46 -0
  89. data/spec/logged_change_spec.rb +480 -0
  90. data/spec/postgresql_replication_spec.rb +48 -0
  91. data/spec/postgresql_support_spec.rb +57 -0
  92. data/spec/progress_bar_spec.rb +77 -0
  93. data/spec/proxied_table_scan_spec.rb +151 -0
  94. data/spec/proxy_block_cursor_spec.rb +197 -0
  95. data/spec/proxy_connection_spec.rb +399 -0
  96. data/spec/proxy_cursor_spec.rb +56 -0
  97. data/spec/proxy_row_cursor_spec.rb +66 -0
  98. data/spec/proxy_runner_spec.rb +70 -0
  99. data/spec/replication_difference_spec.rb +160 -0
  100. data/spec/replication_extender_interface_spec.rb +365 -0
  101. data/spec/replication_extenders_spec.rb +32 -0
  102. data/spec/replication_helper_spec.rb +121 -0
  103. data/spec/replication_initializer_spec.rb +477 -0
  104. data/spec/replication_run_spec.rb +166 -0
  105. data/spec/replication_runner_spec.rb +213 -0
  106. data/spec/replicators_spec.rb +31 -0
  107. data/spec/rubyrep_spec.rb +8 -0
  108. data/spec/scan_detail_reporter_spec.rb +119 -0
  109. data/spec/scan_progress_printers_spec.rb +68 -0
  110. data/spec/scan_report_printers_spec.rb +67 -0
  111. data/spec/scan_runner_spec.rb +50 -0
  112. data/spec/scan_summary_reporter_spec.rb +61 -0
  113. data/spec/session_spec.rb +212 -0
  114. data/spec/spec.opts +1 -0
  115. data/spec/spec_helper.rb +295 -0
  116. data/spec/sync_helper_spec.rb +157 -0
  117. data/spec/sync_runner_spec.rb +78 -0
  118. data/spec/syncers_spec.rb +171 -0
  119. data/spec/table_scan_helper_spec.rb +29 -0
  120. data/spec/table_scan_spec.rb +49 -0
  121. data/spec/table_sorter_spec.rb +31 -0
  122. data/spec/table_spec_resolver_spec.rb +102 -0
  123. data/spec/table_sync_spec.rb +84 -0
  124. data/spec/trigger_mode_switcher_spec.rb +83 -0
  125. data/spec/two_way_replicator_spec.rb +551 -0
  126. data/spec/two_way_syncer_spec.rb +256 -0
  127. data/spec/type_casting_cursor_spec.rb +50 -0
  128. data/spec/uninstall_runner_spec.rb +86 -0
  129. data/tasks/database.rake +439 -0
  130. data/tasks/deployment.rake +29 -0
  131. data/tasks/environment.rake +9 -0
  132. data/tasks/java.rake +37 -0
  133. data/tasks/redmine_test.rake +47 -0
  134. data/tasks/rspec.rake +68 -0
  135. data/tasks/rubyrep.tailor +18 -0
  136. data/tasks/stats.rake +19 -0
  137. data/tasks/task_helper.rb +20 -0
  138. data.tar.gz.sig +0 -0
  139. metadata +243 -0
  140. metadata.gz.sig +0 -0
@@ -0,0 +1,151 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ include RR
4
+
5
+ describe ProxiedTableScan do
6
+ before(:each) do
7
+ Initializer.configuration = deep_copy(proxied_config)
8
+
9
+ # Small block size necessary to exercize all code paths in ProxiedTableScan
10
+ # even when only using tables with very small number of records.
11
+ Initializer.configuration.options[:proxy_block_size] = 2
12
+
13
+ ensure_proxy
14
+ end
15
+
16
+ it "initialize should raise exception if session is not proxied" do
17
+ session = Session.new standard_config
18
+ lambda { ProxiedTableScan.new session, 'dummy_table' } \
19
+ .should raise_error(RuntimeError, /only works with proxied sessions/)
20
+ end
21
+
22
+ it "initialize should cache the primary keys" do
23
+ session = Session.new
24
+ scan = ProxiedTableScan.new session, 'scanner_records'
25
+ scan.primary_key_names.should == ['id']
26
+ end
27
+
28
+ it "initialize should raise exception if table doesn't have primary keys" do
29
+ session = Session.new
30
+ lambda {ProxiedTableScan.new session, 'extender_without_key'} \
31
+ .should raise_error(RuntimeError, /.*extender_without_key.*primary key/)
32
+ end
33
+
34
+ it "block_size should return the :proxy_block_size value of the session options" do
35
+ ProxiedTableScan.new(Session.new, 'scanner_records').block_size \
36
+ .should == 2
37
+ end
38
+
39
+ it "block_size should return the matching table specific option if available" do
40
+ config = Initializer.configuration
41
+ old_table_specific_options = config.tables_with_options
42
+ begin
43
+ config.options = {:proxy_block_size => 2}
44
+ config.include_tables 'scanner_records', {:proxy_block_size => 3}
45
+ ProxiedTableScan.new(Session.new(config), 'scanner_records').block_size \
46
+ .should == 3
47
+ ensure
48
+ config.instance_eval {@tables_with_options = old_table_specific_options}
49
+ end
50
+ end
51
+
52
+ # Creates, prepares and returns a +ProxyBlockCursor+ for the given database
53
+ # +connection+ and +table+.
54
+ # Sets the ProxyBlockCursor#max_row_cache_size as per method parameter.
55
+ def get_block_cursor(connection, table, max_row_cache_size = 1000000)
56
+ cursor = ProxyBlockCursor.new connection, table
57
+ cursor.max_row_cache_size = max_row_cache_size
58
+ cursor.prepare_fetch
59
+ cursor.checksum :proxy_block_size => 1000
60
+ cursor
61
+ end
62
+
63
+ it "compare_blocks should compare all the records in the range" do
64
+ session = Session.new
65
+
66
+ left_cursor = get_block_cursor session.left, 'scanner_records'
67
+ right_cursor = get_block_cursor session.right, 'scanner_records'
68
+
69
+ scan = ProxiedTableScan.new session, 'scanner_records'
70
+ diff = []
71
+ scan.compare_blocks(left_cursor, right_cursor) do |type, row|
72
+ diff.push [type, row]
73
+ end
74
+ # in this scenario the right table has the 'highest' data,
75
+ # so 'right-sided' data are already implicitely tested here
76
+ diff.should == [
77
+ [:conflict, [
78
+ {'id' => 2, 'name' => 'Bob - left database version'},
79
+ {'id' => 2, 'name' => 'Bob - right database version'}]],
80
+ [:left, {'id' => 3, 'name' => 'Charlie - exists in left database only'}],
81
+ [:right, {'id' => 4, 'name' => 'Dave - exists in right database only'}],
82
+ [:left, {'id' => 5, 'name' => 'Eve - exists in left database only'}],
83
+ [:right, {'id' => 6, 'name' => 'Fred - exists in right database only'}]
84
+ ]
85
+ end
86
+
87
+ it "compare_blocks should destroy the created cursors" do
88
+ session = Session.new
89
+
90
+ left_cursor = get_block_cursor session.left, 'scanner_records', 0
91
+ right_cursor = get_block_cursor session.right, 'scanner_records', 0
92
+
93
+ scan = ProxiedTableScan.new session, 'scanner_records'
94
+ scan.compare_blocks(left_cursor, right_cursor) { |type, row| }
95
+
96
+ session.left.cursors.should == {}
97
+ session.right.cursors.should == {}
98
+ end
99
+
100
+ it "run should only call compare single rows if there are different block checksums" do
101
+ config = deep_copy(proxied_config)
102
+ config.right = config.left
103
+ session = Session.new config
104
+ scan = ProxiedTableScan.new session, 'scanner_records'
105
+ scan.should_not_receive(:compare_blocks)
106
+ diff = []
107
+ scan.run do |type, row|
108
+ diff.push [type,row]
109
+ end
110
+ diff.should == []
111
+ end
112
+
113
+ it "run should compare all the records in the table" do
114
+ session = Session.new
115
+ scan = ProxiedTableScan.new session, 'scanner_records'
116
+ diff = []
117
+ scan.run do |type, row|
118
+ diff.push [type, row]
119
+ end
120
+ # in this scenario the right table has the 'highest' data,
121
+ # so 'right-sided' data are already implicitely tested here
122
+ diff.should == [
123
+ [:conflict, [
124
+ {'id' => 2, 'name' => 'Bob - left database version'},
125
+ {'id' => 2, 'name' => 'Bob - right database version'}]],
126
+ [:left, {'id' => 3, 'name' => 'Charlie - exists in left database only'}],
127
+ [:right, {'id' => 4, 'name' => 'Dave - exists in right database only'}],
128
+ [:left, {'id' => 5, 'name' => 'Eve - exists in left database only'}],
129
+ [:right, {'id' => 6, 'name' => 'Fred - exists in right database only'}]
130
+ ]
131
+ end
132
+
133
+ it "run should update the progress" do
134
+ session = Session.new
135
+ scan = ProxiedTableScan.new session, 'scanner_records'
136
+ number_steps = 0
137
+ scan.should_receive(:update_progress).any_number_of_times do |steps|
138
+ number_steps += steps
139
+ end
140
+ scan.run {|_, _|}
141
+ number_steps.should == 8
142
+ end
143
+
144
+ it "run should update the progress even if there are no records" do
145
+ # it should do that to ensure the progress bar is printed
146
+ scan = ProxiedTableScan.new Session.new, 'extender_no_record'
147
+ scan.should_receive(:update_progress).at_least(:once)
148
+ scan.run {|_, _|}
149
+ end
150
+ end
151
+
@@ -0,0 +1,197 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ include RR
4
+
5
+ describe ProxyBlockCursor do
6
+ before(:each) do
7
+ @session = create_mock_proxy_connection 'dummy_table', ['dummy_id']
8
+ @cursor = ProxyBlockCursor.new @session, 'dummy_table'
9
+ end
10
+
11
+ it "initialize should super to ProxyCursor" do
12
+ @cursor.table.should == 'dummy_table'
13
+ end
14
+
15
+ it "next? should return true if there is an already loaded unprocessed row" do
16
+ @cursor.last_row = :dummy_row
17
+ @cursor.next?.should be_true
18
+ end
19
+
20
+ it "next? should return true if the database cursor has more rows" do
21
+ table_cursor = mock("DBCursor")
22
+ table_cursor.should_receive(:next?).and_return(true)
23
+ @cursor.cursor = table_cursor
24
+
25
+ @cursor.next?.should be_true
26
+ end
27
+
28
+ it "next? should return false if there are no loaded or unloaded unprocessed rows" do
29
+ table_cursor = mock("DBCursor")
30
+ table_cursor.should_receive(:next?).and_return(false)
31
+ @cursor.cursor = table_cursor
32
+
33
+ @cursor.next?.should be_false
34
+ end
35
+
36
+ it "next_row should return last loaded unprocessed row or nil if there is none" do
37
+ @cursor.last_row = :dummy_row
38
+
39
+ @cursor.next_row.should == :dummy_row
40
+ @cursor.last_row.should be_nil
41
+ end
42
+
43
+ it "next_row should return next row in database if there is no loaded unprocessed row available" do
44
+ table_cursor = mock("DBCursor")
45
+ table_cursor.should_receive(:next_row).and_return(:dummy_row)
46
+ @cursor.cursor = table_cursor
47
+
48
+ @cursor.next_row.should == :dummy_row
49
+ end
50
+
51
+ it "reset_checksum should create a new empty SHA1 digest" do
52
+ @cursor.digest = :dummy_digest
53
+ @cursor.reset_checksum
54
+ @cursor.digest.should be_an_instance_of(Digest::SHA1)
55
+ end
56
+
57
+ it "reset_checksum should reset block variables" do
58
+ @cursor.reset_checksum
59
+ @cursor.row_checksums.should == []
60
+ @cursor.current_row_cache_size.should == 0
61
+ @cursor.row_cache.should == {}
62
+
63
+ end
64
+
65
+ it "update_checksum should update the existing digests" do
66
+ dummy_row1 = {'dummy_id' => 'dummy_value1'}
67
+ dummy_row2 = {'dummy_id' => 'dummy_value2'}
68
+
69
+ @cursor.reset_checksum
70
+ @cursor.update_checksum dummy_row1
71
+ @cursor.update_checksum dummy_row2
72
+
73
+ @cursor.current_checksum.should == Digest::SHA1.hexdigest(Marshal.dump(dummy_row1) + Marshal.dump(dummy_row2))
74
+ @cursor.row_checksums.should == [
75
+ {:row_keys => dummy_row1, :checksum => Digest::SHA1.hexdigest(Marshal.dump(dummy_row1))},
76
+ {:row_keys => dummy_row2, :checksum => Digest::SHA1.hexdigest(Marshal.dump(dummy_row2))},
77
+ ]
78
+
79
+ @cursor.row_cache.should == {
80
+ Digest::SHA1.hexdigest(Marshal.dump(dummy_row1)) => Marshal.dump(dummy_row1),
81
+ Digest::SHA1.hexdigest(Marshal.dump(dummy_row2)) => Marshal.dump(dummy_row2)
82
+ }
83
+ end
84
+
85
+ it "retrieve_row_cache should retrieve the specified elements" do
86
+ @cursor.row_cache = {'dummy_checksum' => 'bla'}
87
+ @cursor.retrieve_row_cache(['non_cached_row_checksum', 'dummy_checksum']).should ==
88
+ {'dummy_checksum' => 'bla'}
89
+ end
90
+
91
+ it "current_checksum should return the current checksum" do
92
+ digest = mock("Digest")
93
+ digest.should_receive(:hexdigest).and_return(:dummy_checksum)
94
+ @cursor.digest = digest
95
+
96
+ @cursor.current_checksum.should == :dummy_checksum
97
+ end
98
+
99
+ it "checksum should reset the current digest" do
100
+ @cursor.reset_checksum # need to call it now so that for the call to checksum it can be mocked
101
+ @cursor.should_receive(:reset_checksum)
102
+ @cursor.should_receive(:next?).and_return(false)
103
+ @cursor.checksum :proxy_block_size => 1
104
+ end
105
+
106
+ it "checksum should complain if neither :proxy_block_size nor :max_row are provided" do
107
+ lambda {@cursor.checksum}.should raise_error(
108
+ RuntimeError, 'options must include either :proxy_block_size or :max_row')
109
+ end
110
+
111
+ it "checksum should verify options" do
112
+ lambda {@cursor.checksum}.should raise_error(
113
+ RuntimeError, 'options must include either :proxy_block_size or :max_row')
114
+ lambda {@cursor.checksum(:proxy_block_size => 0)}.should raise_error(
115
+ RuntimeError, ':proxy_block_size must be greater than 0')
116
+ end
117
+
118
+ it "checksum should read maximum :proxy_block_size rows" do
119
+ session = ProxyConnection.new proxied_config.left
120
+
121
+ cursor = ProxyBlockCursor.new session, 'scanner_records'
122
+ cursor.prepare_fetch
123
+
124
+ last_row, = cursor.checksum :proxy_block_size => 2
125
+ last_row.should == {'id' => 2}
126
+
127
+ last_row, = cursor.checksum :proxy_block_size => 1000
128
+ last_row.should == {'id' => 5}
129
+ end
130
+
131
+ it "checksum should read up to the specified :max_row" do
132
+ session = ProxyConnection.new proxied_config.left
133
+
134
+ cursor = ProxyBlockCursor.new session, 'scanner_records'
135
+ cursor.prepare_fetch
136
+
137
+ last_row, = cursor.checksum :max_row => {'id' => 2}
138
+ last_row.should == {'id' => 2}
139
+ last_row, = cursor.checksum :max_row => {'id' => 1000}
140
+ last_row.should == {'id' => 5}
141
+ end
142
+
143
+ it "checksum called with :proxy_block_size should return the correct checksum" do
144
+ session = ProxyConnection.new proxied_config.left
145
+
146
+ cursor = ProxyBlockCursor.new session, 'scanner_records'
147
+ cursor.prepare_fetch
148
+
149
+ last_row , checksum = cursor.checksum :proxy_block_size => 2
150
+
151
+ expected_checksum = Digest::SHA1.hexdigest(
152
+ Marshal.dump('id' => 1, 'name' => 'Alice - exists in both databases') +
153
+ Marshal.dump('id' => 2, 'name' => 'Bob - left database version')
154
+ )
155
+
156
+ checksum.should == expected_checksum
157
+ end
158
+
159
+ it "checksum called with :max_row should return the correct checksum" do
160
+ session = ProxyConnection.new proxied_config.left
161
+
162
+ cursor = ProxyBlockCursor.new session, 'scanner_records'
163
+ cursor.prepare_fetch
164
+
165
+ last_row , checksum = cursor.checksum :max_row => {'id' => 2}
166
+
167
+ expected_checksum = Digest::SHA1.hexdigest(
168
+ Marshal.dump('id' => 1, 'name' => 'Alice - exists in both databases') +
169
+ Marshal.dump('id' => 2, 'name' => 'Bob - left database version')
170
+ )
171
+
172
+ checksum.should == expected_checksum
173
+ end
174
+
175
+ it "checksum called with :proxy_block_size should return the correct row count" do
176
+ session = ProxyConnection.new proxied_config.left
177
+
178
+ cursor = ProxyBlockCursor.new session, 'scanner_records'
179
+ cursor.prepare_fetch
180
+
181
+ _ , _, row_count = cursor.checksum :proxy_block_size => 2
182
+
183
+ row_count.should == 2
184
+ end
185
+
186
+ it "checksum called with :max_row should return the correct row count" do
187
+ session = ProxyConnection.new proxied_config.left
188
+
189
+ cursor = ProxyBlockCursor.new session, 'scanner_records'
190
+ cursor.prepare_fetch
191
+
192
+ _ , _, row_count = cursor.checksum :max_row => {'id' => 2}
193
+
194
+ row_count.should == 2
195
+ end
196
+
197
+ end