rubyrep 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 (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