andyjeffries-rubyrep 1.2.1

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 (125) hide show
  1. data/History.txt +83 -0
  2. data/License.txt +20 -0
  3. data/Manifest.txt +151 -0
  4. data/README.txt +37 -0
  5. data/bin/rubyrep +8 -0
  6. data/lib/rubyrep.rb +72 -0
  7. data/lib/rubyrep/base_runner.rb +195 -0
  8. data/lib/rubyrep/command_runner.rb +144 -0
  9. data/lib/rubyrep/committers/buffered_committer.rb +151 -0
  10. data/lib/rubyrep/committers/committers.rb +152 -0
  11. data/lib/rubyrep/configuration.rb +275 -0
  12. data/lib/rubyrep/connection_extenders/connection_extenders.rb +165 -0
  13. data/lib/rubyrep/connection_extenders/jdbc_extender.rb +65 -0
  14. data/lib/rubyrep/connection_extenders/mysql_extender.rb +59 -0
  15. data/lib/rubyrep/connection_extenders/postgresql_extender.rb +277 -0
  16. data/lib/rubyrep/database_proxy.rb +52 -0
  17. data/lib/rubyrep/direct_table_scan.rb +75 -0
  18. data/lib/rubyrep/generate_runner.rb +105 -0
  19. data/lib/rubyrep/initializer.rb +39 -0
  20. data/lib/rubyrep/log_helper.rb +30 -0
  21. data/lib/rubyrep/logged_change.rb +160 -0
  22. data/lib/rubyrep/logged_change_loader.rb +197 -0
  23. data/lib/rubyrep/noisy_connection.rb +80 -0
  24. data/lib/rubyrep/proxied_table_scan.rb +171 -0
  25. data/lib/rubyrep/proxy_block_cursor.rb +145 -0
  26. data/lib/rubyrep/proxy_connection.rb +431 -0
  27. data/lib/rubyrep/proxy_cursor.rb +44 -0
  28. data/lib/rubyrep/proxy_row_cursor.rb +43 -0
  29. data/lib/rubyrep/proxy_runner.rb +89 -0
  30. data/lib/rubyrep/replication_difference.rb +100 -0
  31. data/lib/rubyrep/replication_extenders/mysql_replication.rb +271 -0
  32. data/lib/rubyrep/replication_extenders/postgresql_replication.rb +236 -0
  33. data/lib/rubyrep/replication_extenders/replication_extenders.rb +26 -0
  34. data/lib/rubyrep/replication_helper.rb +142 -0
  35. data/lib/rubyrep/replication_initializer.rb +327 -0
  36. data/lib/rubyrep/replication_run.rb +142 -0
  37. data/lib/rubyrep/replication_runner.rb +166 -0
  38. data/lib/rubyrep/replicators/replicators.rb +42 -0
  39. data/lib/rubyrep/replicators/two_way_replicator.rb +361 -0
  40. data/lib/rubyrep/scan_progress_printers/progress_bar.rb +65 -0
  41. data/lib/rubyrep/scan_progress_printers/scan_progress_printers.rb +65 -0
  42. data/lib/rubyrep/scan_report_printers/scan_detail_reporter.rb +111 -0
  43. data/lib/rubyrep/scan_report_printers/scan_report_printers.rb +67 -0
  44. data/lib/rubyrep/scan_report_printers/scan_summary_reporter.rb +75 -0
  45. data/lib/rubyrep/scan_runner.rb +25 -0
  46. data/lib/rubyrep/session.rb +230 -0
  47. data/lib/rubyrep/sync_helper.rb +121 -0
  48. data/lib/rubyrep/sync_runner.rb +31 -0
  49. data/lib/rubyrep/syncers/syncers.rb +112 -0
  50. data/lib/rubyrep/syncers/two_way_syncer.rb +174 -0
  51. data/lib/rubyrep/table_scan.rb +54 -0
  52. data/lib/rubyrep/table_scan_helper.rb +46 -0
  53. data/lib/rubyrep/table_sorter.rb +70 -0
  54. data/lib/rubyrep/table_spec_resolver.rb +142 -0
  55. data/lib/rubyrep/table_sync.rb +90 -0
  56. data/lib/rubyrep/task_sweeper.rb +77 -0
  57. data/lib/rubyrep/trigger_mode_switcher.rb +63 -0
  58. data/lib/rubyrep/type_casting_cursor.rb +31 -0
  59. data/lib/rubyrep/uninstall_runner.rb +93 -0
  60. data/lib/rubyrep/version.rb +9 -0
  61. data/rubyrep +8 -0
  62. data/rubyrep.bat +4 -0
  63. data/setup.rb +1585 -0
  64. data/spec/base_runner_spec.rb +218 -0
  65. data/spec/buffered_committer_spec.rb +274 -0
  66. data/spec/command_runner_spec.rb +145 -0
  67. data/spec/committers_spec.rb +178 -0
  68. data/spec/configuration_spec.rb +203 -0
  69. data/spec/connection_extender_interface_spec.rb +141 -0
  70. data/spec/connection_extenders_registration_spec.rb +164 -0
  71. data/spec/database_proxy_spec.rb +48 -0
  72. data/spec/database_rake_spec.rb +40 -0
  73. data/spec/db_specific_connection_extenders_spec.rb +34 -0
  74. data/spec/db_specific_replication_extenders_spec.rb +38 -0
  75. data/spec/direct_table_scan_spec.rb +61 -0
  76. data/spec/dolphins.jpg +0 -0
  77. data/spec/generate_runner_spec.rb +84 -0
  78. data/spec/initializer_spec.rb +46 -0
  79. data/spec/log_helper_spec.rb +39 -0
  80. data/spec/logged_change_loader_spec.rb +68 -0
  81. data/spec/logged_change_spec.rb +470 -0
  82. data/spec/noisy_connection_spec.rb +78 -0
  83. data/spec/postgresql_replication_spec.rb +48 -0
  84. data/spec/postgresql_schema_support_spec.rb +212 -0
  85. data/spec/postgresql_support_spec.rb +63 -0
  86. data/spec/progress_bar_spec.rb +77 -0
  87. data/spec/proxied_table_scan_spec.rb +151 -0
  88. data/spec/proxy_block_cursor_spec.rb +197 -0
  89. data/spec/proxy_connection_spec.rb +423 -0
  90. data/spec/proxy_cursor_spec.rb +56 -0
  91. data/spec/proxy_row_cursor_spec.rb +66 -0
  92. data/spec/proxy_runner_spec.rb +70 -0
  93. data/spec/replication_difference_spec.rb +161 -0
  94. data/spec/replication_extender_interface_spec.rb +367 -0
  95. data/spec/replication_extenders_spec.rb +32 -0
  96. data/spec/replication_helper_spec.rb +178 -0
  97. data/spec/replication_initializer_spec.rb +509 -0
  98. data/spec/replication_run_spec.rb +443 -0
  99. data/spec/replication_runner_spec.rb +254 -0
  100. data/spec/replicators_spec.rb +36 -0
  101. data/spec/rubyrep_spec.rb +8 -0
  102. data/spec/scan_detail_reporter_spec.rb +119 -0
  103. data/spec/scan_progress_printers_spec.rb +68 -0
  104. data/spec/scan_report_printers_spec.rb +67 -0
  105. data/spec/scan_runner_spec.rb +50 -0
  106. data/spec/scan_summary_reporter_spec.rb +61 -0
  107. data/spec/session_spec.rb +253 -0
  108. data/spec/spec.opts +1 -0
  109. data/spec/spec_helper.rb +305 -0
  110. data/spec/strange_name_support_spec.rb +135 -0
  111. data/spec/sync_helper_spec.rb +169 -0
  112. data/spec/sync_runner_spec.rb +78 -0
  113. data/spec/syncers_spec.rb +171 -0
  114. data/spec/table_scan_helper_spec.rb +36 -0
  115. data/spec/table_scan_spec.rb +49 -0
  116. data/spec/table_sorter_spec.rb +30 -0
  117. data/spec/table_spec_resolver_spec.rb +111 -0
  118. data/spec/table_sync_spec.rb +140 -0
  119. data/spec/task_sweeper_spec.rb +47 -0
  120. data/spec/trigger_mode_switcher_spec.rb +83 -0
  121. data/spec/two_way_replicator_spec.rb +721 -0
  122. data/spec/two_way_syncer_spec.rb +256 -0
  123. data/spec/type_casting_cursor_spec.rb +50 -0
  124. data/spec/uninstall_runner_spec.rb +93 -0
  125. metadata +190 -0
@@ -0,0 +1 @@
1
+ --colour
@@ -0,0 +1,305 @@
1
+ begin
2
+ require 'spec'
3
+ rescue LoadError
4
+ require 'rubygems'
5
+ gem 'rspec'
6
+ require 'spec'
7
+ end
8
+
9
+ require 'drb'
10
+ require 'digest/sha1'
11
+
12
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), "..", "lib")
13
+ $LOAD_PATH.unshift File.dirname(__FILE__)
14
+
15
+ require 'rubyrep'
16
+ require 'connection_extender_interface_spec'
17
+
18
+ unless self.class.const_defined?('STRANGE_TABLE')
19
+ if ENV['RR_TEST_DB'] == 'postgres' || ENV['RR_TEST_DB'] == nil
20
+ STRANGE_TABLE = 'table_with.stränge Name山'
21
+ else
22
+ STRANGE_TABLE = 'table_with_stränge Name山'
23
+ end
24
+ STRANGE_COLUMN = 'stränge. Column山'
25
+ end
26
+
27
+ class Module
28
+ # Used to verify that an instance of the class / module receives a call of the
29
+ # specified method.
30
+ # This is for cases where a method call has to be mocked of an object that is
31
+ # not yet created.
32
+ # (Couldn't find out how to do that using existing rspec mocking features.)
33
+ def any_instance_should_receive(method, &blck)
34
+ tmp_method = "original_before_mocking_#{method}".to_sym
35
+ logger_key = "#{self.name}_#{method}"
36
+ $mock_method_marker ||= {}
37
+ $mock_method_marker[logger_key] = Spec::Mocks::Mock.new("#{name} Instance")
38
+ $mock_method_marker[logger_key].should_receive(method).at_least(:once)
39
+ self.send :alias_method, tmp_method, method
40
+ self.class_eval "def #{method}(*args); $mock_method_marker['#{logger_key}'].#{method}; end"
41
+ blck.call
42
+ ensure
43
+ $mock_method_marker.delete logger_key
44
+ self.send :alias_method, method, tmp_method rescue nil
45
+ end
46
+
47
+ # Used to verify that an instance of the class / module does not receive a
48
+ # call of the specified method.
49
+ # This is for cases where a method call has to be mocked of an object that is
50
+ # not yet created.
51
+ # (Couldn't find out how to do that using existing rspec mocking features.)
52
+ def any_instance_should_not_receive(method, &blck)
53
+ tmp_method = "original_before_mocking_#{method}".to_sym
54
+ logger_key = "#{self.name}_#{method}"
55
+ $mock_method_marker ||= {}
56
+ $mock_method_marker[logger_key] = Spec::Mocks::Mock.new("#{name} Instance")
57
+ $mock_method_marker[logger_key].should_not_receive(method)
58
+ self.send :alias_method, tmp_method, method
59
+ self.class_eval "def #{method}(*args); $mock_method_marker['#{logger_key}'].#{method}; end"
60
+ blck.call
61
+ ensure
62
+ $mock_method_marker.delete logger_key
63
+ self.send :alias_method, method, tmp_method rescue nil
64
+ end
65
+ end
66
+
67
+ class RR::Session
68
+ # To keep rspec output of failed tests managable
69
+ def inspect; 'session'; end
70
+ end
71
+
72
+ class ActiveRecord::Base
73
+ class << self
74
+ # Hack:
75
+ # The default inspect method (as per activerecord version 2.2.2) tries to
76
+ # send commands to the database.
77
+ # This leads to rcov failing.
78
+ # As workaround this is disabling the attempts to connect to the database.
79
+ def inspect
80
+ super
81
+ end
82
+ end
83
+ end
84
+
85
+ # If number_of_calls is :once, mock ActiveRecord for 1 call.
86
+ # If number_of_calls is :twice, mock ActiveRecord for 2 calls.
87
+ def mock_active_record(number_of_calls)
88
+ ConnectionExtenders::DummyActiveRecord.should_receive(:establish_connection).send(number_of_calls) \
89
+ .and_return {|config| $used_config = config}
90
+
91
+ dummy_connection = Object.new
92
+ # We have a spec testing behaviour for non-existing extenders.
93
+ # So extend might not be called in all cases
94
+ dummy_connection.stub!(:extend)
95
+ dummy_connection.stub!(:tables).and_return([])
96
+ dummy_connection.stub!(:initialize_search_path)
97
+ dummy_connection.stub!(:select_one).and_return({'x' => '2'})
98
+
99
+ ConnectionExtenders::DummyActiveRecord.should_receive(:connection).send(number_of_calls) \
100
+ .and_return {dummy_connection}
101
+ end
102
+
103
+ # Creates a mock ProxyConnection with the given
104
+ # * mock_table: name of the mock table
105
+ # * primary_key_names: array of mock primary column names
106
+ # * column_names: array of mock column names, if nil: doesn't mock this function
107
+ def create_mock_proxy_connection(mock_table, primary_key_names, column_names = nil)
108
+ session = mock("ProxyConnection")
109
+ if primary_key_names
110
+ session.should_receive(:primary_key_names) \
111
+ .with(mock_table) \
112
+ .and_return(primary_key_names)
113
+ end
114
+ if column_names
115
+ session.should_receive(:column_names) \
116
+ .with(mock_table) \
117
+ .and_return(column_names)
118
+ end
119
+ session.should_receive(:quote_value) \
120
+ .any_number_of_times \
121
+ .with(an_instance_of(String), an_instance_of(String), anything) \
122
+ .and_return { |table, column, value| value}
123
+
124
+ session.should_receive(:connection) \
125
+ .any_number_of_times \
126
+ .and_return {dummy_connection}
127
+
128
+ session.should_receive(:quote_column_name) \
129
+ .any_number_of_times \
130
+ .with(an_instance_of(String)) \
131
+ .and_return { |column_name| "'#{column_name}'" }
132
+
133
+ session.should_receive(:quote_table_name) \
134
+ .any_number_of_times \
135
+ .with(an_instance_of(String)) \
136
+ .and_return { |table_name| "'#{table_name}'" }
137
+
138
+ session
139
+ end
140
+
141
+ # Turns an SQL query into a regular expression:
142
+ # * Handles quotes (differing depending on DBMS).
143
+ # * Handles round brackets (escaping with backslash to make them literals).
144
+ # * Removes line breaks and double spaces
145
+ # (allowing use of intendation and line continuation)
146
+ # Returns the regular expression created from the provided +sql+ string.
147
+ def sql_to_regexp(sql)
148
+ Regexp.new(sql.strip.squeeze(" ") \
149
+ .gsub("(", "\\(").gsub(")", "\\)") \
150
+ .gsub("'", 'E?.') \
151
+ .gsub('"', 'E?.'))
152
+ end
153
+
154
+ # Returns a deep copy of the provided object. Works also for Proc objects or
155
+ # objects referencing Proc objects.
156
+ def deep_copy(object)
157
+ Proc.send :define_method, :_dump, lambda { |depth|
158
+ @@proc_store ||= {}
159
+ @@proc_key ||= "000000000"
160
+ @@proc_key.succ!
161
+ @@proc_store[@@proc_key] = self
162
+ @@proc_key
163
+ }
164
+ Proc.class.send :define_method, :_load, lambda { |key|
165
+ proc = @@proc_store[key]
166
+ @@proc_store.delete key
167
+ proc
168
+ }
169
+
170
+ Marshal.restore(Marshal.dump(object))
171
+ ensure
172
+ Proc.send :remove_method, :_dump if Proc.method_defined? :_dump
173
+ Proc.class.send :remove_method, :_load if Proc.class.method_defined? :_load
174
+ end
175
+
176
+ # Allows the temporary faking of RUBY_PLATFORM to the given value
177
+ # Needs to be called with a block. While the block is executed, RUBY_PLATFORM
178
+ # is set to the given fake value
179
+ def fake_ruby_platform(fake_ruby_platform)
180
+ old_ruby_platform = RUBY_PLATFORM
181
+ old_verbose, $VERBOSE = $VERBOSE, nil
182
+ Object.const_set 'RUBY_PLATFORM', fake_ruby_platform
183
+ $VERBOSE = old_verbose
184
+ yield
185
+ ensure
186
+ $VERBOSE = nil
187
+ Object.const_set 'RUBY_PLATFORM', old_ruby_platform
188
+ $VERBOSE = old_verbose
189
+ end
190
+
191
+ # Reads the database configuration from the config folder for the specified config key
192
+ # E.g. if config is :postgres, tries to read the config from 'postgres_config.rb'
193
+ def read_config(config)
194
+ $config_cache ||= {}
195
+ cache_key = "#{config.to_s}_#{ENV['RR_TEST_DB']}"
196
+ unless $config_cache[cache_key]
197
+ # load the proxied config but ensure that the original configuration is restored
198
+ old_config = RR::Initializer.configuration
199
+ RR::Initializer.reset
200
+ begin
201
+ load File.dirname(__FILE__) + "/../config/#{config}_config.rb"
202
+ $config_cache[cache_key] = RR::Initializer.configuration
203
+ ensure
204
+ RR::Initializer.configuration = old_config
205
+ end
206
+ end
207
+ $config_cache[cache_key]
208
+ end
209
+
210
+ # Removes all cached database configurations
211
+ def clear_config_cache
212
+ $config_cache = {}
213
+ end
214
+
215
+ # Retrieves the proxied database config as specified in config/proxied_test_config.rb
216
+ def proxied_config
217
+ read_config :proxied_test
218
+ end
219
+
220
+ # Retrieves the standard (non-proxied) database config as specified in config/test_config.rb
221
+ def standard_config
222
+ read_config :test
223
+ end
224
+
225
+ # Inserts two records into 'sequence_test' and returns the generated id values
226
+ # * session: the active Session
227
+ # * table: name of the table which is to be tested
228
+ def get_example_sequence_values(session, table = 'sequence_test')
229
+ session.left.insert_record table, { 'name' => 'bla' }
230
+ id1 = session.left.select_one("select max(id) as id from #{table}")['id'].to_i
231
+ session.left.insert_record table, { 'name' => 'blub' }
232
+ id2 = session.left.select_one("select max(id) as id from #{table}")['id'].to_i
233
+ return id1, id2
234
+ end
235
+
236
+ # If true, start proxy as external process (more realistic test but also slower).
237
+ # Otherwise start in the current process as thread.
238
+ $start_proxy_as_external_process ||= false
239
+
240
+ # Starts a proxy under the given host and post
241
+ def start_proxy(host, port)
242
+ if $start_proxy_as_external_process
243
+ bin_path = File.join(File.dirname(__FILE__), "..", "bin", "rubyrep")
244
+ ruby = RUBY_PLATFORM =~ /java/ ? 'jruby' : 'ruby'
245
+ cmd = "#{ruby} #{bin_path} proxy -h #{host} -p #{port}"
246
+ Thread.new {system cmd}
247
+ else
248
+ url = "druby://#{host}:#{port}"
249
+ DRb.start_service(url, DatabaseProxy.new)
250
+ end
251
+ end
252
+
253
+ # Set to true if the proxy as per SPEC_PROXY_CONFIG is running
254
+ $proxy_confirmed_running = false
255
+
256
+ # Starts a proxy as per left proxy settings defined in config/proxied_test_config.rb.
257
+ # Only starts the proxy though if none is running yet at the according host / port.
258
+ # If it starts a proxy child process, it also prepares automatic termination
259
+ # after the spec run is finished.
260
+ def ensure_proxy
261
+ # only execute the network verification once per spec run
262
+ unless $proxy_confirmed_running
263
+ drb_url = "druby://#{proxied_config.left[:proxy_host]}:#{proxied_config.left[:proxy_port]}"
264
+ # try to connect to the proxy
265
+ begin
266
+ proxy = DRbObject.new nil, drb_url
267
+ proxy.ping
268
+ $proxy_confirmed_running = true
269
+ rescue DRb::DRbConnError => e
270
+ # Proxy not yet running ==> start it
271
+ start_proxy proxied_config.left[:proxy_host], proxied_config.left[:proxy_port]
272
+
273
+ maximum_startup_time = 5 # maximum time in seconds for the proxy to start
274
+ waiting_time = 0.1 # time to wait between connection attempts
275
+
276
+ time = 0.0
277
+ ping_response = ''
278
+ # wait for the proxy to start up and become operational
279
+ while ping_response != 'pong' and time < maximum_startup_time
280
+ begin
281
+ proxy = DRbObject.new nil, drb_url
282
+ ping_response = proxy.ping
283
+ break
284
+ rescue DRb::DRbConnError => e
285
+ # do nothing (just try again)
286
+ end
287
+ sleep waiting_time
288
+ time += waiting_time
289
+ end
290
+ if ping_response == 'pong'
291
+ #puts "Proxy started (took #{time} seconds)"
292
+ # Ensure that the started proxy is terminated with the completion of the spec run.
293
+ at_exit do
294
+ proxy = DRbObject.new nil, drb_url
295
+ proxy.terminate! rescue DRb::DRbConnError
296
+ end if $start_proxy_as_external_process
297
+ else
298
+ raise "Could not start proxy"
299
+ end
300
+ end
301
+
302
+ # if we got till here, then a proxy is running or was successfully started
303
+ $proxy_confirmed_running = true
304
+ end
305
+ end
@@ -0,0 +1,135 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ include RR
4
+
5
+ describe "Unusual table and column name support" do
6
+ before(:each) do
7
+ Initializer.configuration = standard_config
8
+ end
9
+
10
+ it "should be able to insert, update and delete records" do
11
+ session = Session.new
12
+ session.left.begin_db_transaction
13
+ begin
14
+ select_row = Proc.new {
15
+ session.left.
16
+ select_one(
17
+ "select #{session.left.quote_column_name(STRANGE_COLUMN)}
18
+ from #{session.left.quote_table_name(STRANGE_TABLE)}")
19
+ }
20
+
21
+ session.left.insert_record STRANGE_TABLE, 'id' => 1, STRANGE_COLUMN => 'bla'
22
+ select_row.call[STRANGE_COLUMN].should == 'bla'
23
+
24
+ session.left.update_record STRANGE_TABLE, 'id' => 1, STRANGE_COLUMN => 'blub'
25
+ select_row.call[STRANGE_COLUMN].should == 'blub'
26
+
27
+ session.left.delete_record STRANGE_TABLE, 'id' => 1
28
+ select_row.call.should be_nil
29
+ ensure
30
+ session.left.rollback_db_transaction
31
+ end
32
+ end
33
+
34
+ it "should be able to identify primary keys" do
35
+ session = Session.new
36
+ session.left.primary_key_names(STRANGE_TABLE).should == ['id']
37
+ end
38
+
39
+ it "should be able to identify referenced tables" do
40
+ session = Session.new
41
+ referenced_tables = session.left.referenced_tables([STRANGE_TABLE])
42
+ referenced_tables.size.should == 1
43
+ referenced_tables[STRANGE_TABLE].sort.
44
+ should == ["referenced_table"]
45
+ end
46
+
47
+ it "should support sequence operations" do
48
+ session = Session.new
49
+ begin
50
+ left_sequence_values = session.left.sequence_values('rr', STRANGE_TABLE)
51
+ right_sequence_values = session.right.sequence_values('rr', STRANGE_TABLE)
52
+ session.left.update_sequences(
53
+ 'rr', STRANGE_TABLE, 10, 7, left_sequence_values, right_sequence_values, 1)
54
+
55
+ sequence_props = session.left.sequence_values('rr', STRANGE_TABLE).values.first
56
+ sequence_props[:increment].should == 10
57
+ (sequence_props[:value] % 10).should == 7
58
+
59
+ session.left.clear_sequence_setup 'rr', STRANGE_TABLE
60
+
61
+ session.left.sequence_values('rr', STRANGE_TABLE).values.first[:increment].should == 1
62
+ ensure
63
+ session.left.clear_sequence_setup 'rr', STRANGE_TABLE
64
+ end
65
+ end
66
+
67
+ it "should support trigger operations for strange tables" do
68
+ trigger_name = 'rr_' + STRANGE_TABLE
69
+ session = Session.new
70
+ begin
71
+ session.left.replication_trigger_exists?(trigger_name, STRANGE_TABLE).should be_false
72
+ session.left.create_replication_trigger({
73
+ :trigger_name => trigger_name,
74
+ :table => STRANGE_TABLE,
75
+ :keys => ['id'],
76
+ :log_table => 'rr_pending_changes',
77
+ :activity_table => 'rr_running_flags',
78
+ :key_sep => '|',
79
+ :exclude_rr_activity => true
80
+ })
81
+ session.left.replication_trigger_exists?(trigger_name, STRANGE_TABLE).should be_true
82
+ session.left.insert_record STRANGE_TABLE, {
83
+ 'id' => 11,
84
+ STRANGE_COLUMN => 'blub'
85
+ }
86
+ log_record = session.left.select_one(
87
+ "select * from rr_pending_changes where change_table = '#{STRANGE_TABLE}'")
88
+ log_record['change_key'].should == 'id|11'
89
+ log_record['change_type'].should == 'I'
90
+
91
+ session.left.drop_replication_trigger trigger_name, STRANGE_TABLE
92
+ session.left.replication_trigger_exists?(trigger_name, STRANGE_TABLE).should be_false
93
+ ensure
94
+ if session.left.replication_trigger_exists?(trigger_name, STRANGE_TABLE)
95
+ session.left.drop_replication_trigger trigger_name, STRANGE_TABLE
96
+ end
97
+ session.left.execute "delete from #{session.left.quote_table_name(STRANGE_TABLE)}"
98
+ session.left.execute "delete from rr_pending_changes"
99
+ end
100
+ end
101
+
102
+ it "should support trigger operations for table with strange primary keys" do
103
+ trigger_name = 'rr_table_with_strange_key'
104
+ session = Session.new
105
+ begin
106
+ session.left.replication_trigger_exists?(trigger_name, :table_with_strange_key).should be_false
107
+ session.left.create_replication_trigger({
108
+ :trigger_name => trigger_name,
109
+ :table => :table_with_strange_key,
110
+ :keys => [STRANGE_COLUMN],
111
+ :log_table => 'rr_pending_changes',
112
+ :activity_table => 'rr_running_flags',
113
+ :key_sep => '|',
114
+ :exclude_rr_activity => true
115
+ })
116
+ session.left.replication_trigger_exists?(trigger_name, :table_with_strange_key).should be_true
117
+ session.left.insert_record 'table_with_strange_key', {
118
+ STRANGE_COLUMN => '11'
119
+ }
120
+ log_record = session.left.select_one(
121
+ "select * from rr_pending_changes where change_table = 'table_with_strange_key'")
122
+ log_record['change_key'].should == "#{STRANGE_COLUMN}|11"
123
+ log_record['change_type'].should == 'I'
124
+
125
+ session.left.drop_replication_trigger trigger_name, :table_with_strange_key
126
+ session.left.replication_trigger_exists?(trigger_name, :table_with_strange_key).should be_false
127
+ ensure
128
+ if session.left.replication_trigger_exists?(trigger_name, :table_with_strange_key)
129
+ session.left.drop_replication_trigger trigger_name, :table_with_strange_key
130
+ end
131
+ session.left.execute "delete from #{session.left.quote_table_name(:table_with_strange_key)}"
132
+ session.left.execute "delete from rr_pending_changes"
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,169 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ include RR
4
+
5
+ describe SyncHelper do
6
+ before(:each) do
7
+ Initializer.configuration = standard_config
8
+ end
9
+
10
+ it "initialize should initialize the correct committer" do
11
+ sync = TableSync.new(Session.new, 'scanner_records')
12
+ helper = SyncHelper.new(sync)
13
+ c = helper.instance_eval {committer}
14
+ c.should be_an_instance_of(Committers::DefaultCommitter)
15
+ c.session.should == helper.session
16
+ end
17
+
18
+ it "session should return the session" do
19
+ sync = TableSync.new(Session.new, 'scanner_records')
20
+ helper = SyncHelper.new(sync)
21
+ helper.session.should == sync.session
22
+ end
23
+
24
+ it "extract_key should extract the primary key column_name => value pairs" do
25
+ sync = TableSync.new(Session.new, 'extender_combined_key')
26
+ helper = SyncHelper.new(sync)
27
+ helper.extract_key('first_id' => 1, 'second_id' => 2, 'name' => 'bla').
28
+ should == {'first_id' => 1, 'second_id' => 2}
29
+ end
30
+
31
+ it "ensure_event_log should ask the replication_initializer to ensure the event log" do
32
+ sync = TableSync.new(Session.new, 'scanner_records')
33
+ helper = SyncHelper.new(sync)
34
+ ReplicationInitializer.any_instance_should_receive(:ensure_event_log) do
35
+ helper.ensure_event_log
36
+ end
37
+ end
38
+
39
+ it "log_sync_outcome should log the replication outcome correctly" do
40
+ session = Session.new
41
+ session.left.begin_db_transaction
42
+ begin
43
+ sync = TableSync.new(Session.new, 'scanner_records')
44
+ helper = SyncHelper.new(sync)
45
+
46
+ # Verify that the log information are made fitting
47
+ helper.should_receive(:fit_description_columns).
48
+ with('my_outcome', 'my_long_description').
49
+ and_return(['my_outcomeX', 'my_long_descriptionY'])
50
+
51
+ helper.log_sync_outcome(
52
+ {'bla' => 'blub', 'id' => 1},
53
+ 'my_sync_type',
54
+ 'my_outcome',
55
+ 'my_long_description'
56
+ )
57
+
58
+ row = session.left.select_one("select * from rr_logged_events order by id desc")
59
+ row['activity'].should == 'sync'
60
+ row['change_table'].should == 'scanner_records'
61
+ row['diff_type'].should == 'my_sync_type'
62
+ row['change_key'].should == '1'
63
+ row['left_change_type'].should be_nil
64
+ row['right_change_type'].should be_nil
65
+ row['description'].should == 'my_outcomeX'
66
+ row['long_description'].should == 'my_long_descriptionY'
67
+ Time.parse(row['event_time']).should >= 10.seconds.ago
68
+ row['diff_dump'].should == nil
69
+ ensure
70
+ session.left.rollback_db_transaction if session
71
+ end
72
+ end
73
+
74
+ it "log_sync_outcome should log events for combined primary key tables correctly" do
75
+ session = Session.new
76
+ session.left.begin_db_transaction
77
+ begin
78
+ sync = TableSync.new(Session.new, 'extender_combined_key')
79
+ helper = SyncHelper.new(sync)
80
+
81
+ helper.log_sync_outcome(
82
+ {'bla' => 'blub', 'first_id' => 1, 'second_id' => 2},
83
+ 'my_sync_type',
84
+ 'my_outcome',
85
+ 'my_long_description'
86
+ )
87
+
88
+ row = session.left.select_one("select * from rr_logged_events order by id desc")
89
+ row['change_key'].should == '"first_id"=>"1", "second_id"=>"2"'
90
+ ensure
91
+ session.left.rollback_db_transaction if session
92
+ end
93
+ end
94
+
95
+ it "left_table and right_table should return the correct table names" do
96
+ sync = TableSync.new(Session.new, 'scanner_records')
97
+ helper = SyncHelper.new(sync)
98
+ helper.left_table.should == 'scanner_records'
99
+ helper.right_table.should == 'scanner_records'
100
+
101
+ sync = TableSync.new(Session.new, 'scanner_records', 'right_table')
102
+ helper = SyncHelper.new(sync)
103
+ helper.left_table.should == 'scanner_records'
104
+ helper.right_table.should == 'right_table'
105
+ end
106
+
107
+ it "tables should return the correct table name hash" do
108
+ sync = TableSync.new(Session.new, 'scanner_records', 'right_table')
109
+ helper = SyncHelper.new(sync)
110
+ helper.tables.should == {:left => 'scanner_records', :right => 'right_table'}
111
+ end
112
+
113
+ it "table_sync should return the current table sync instance" do
114
+ sync = TableSync.new(Session.new, 'scanner_records')
115
+ helper = SyncHelper.new(sync)
116
+ helper.table_sync.should == sync
117
+ end
118
+
119
+ it "sync_options should return the correct sync options" do
120
+ sync = TableSync.new(Session.new, 'scanner_records')
121
+ helper = SyncHelper.new(sync)
122
+ helper.sync_options.should == sync.sync_options
123
+ end
124
+
125
+ it "insert_record should insert the given record" do
126
+ sync = TableSync.new(Session.new, 'scanner_records')
127
+ helper = SyncHelper.new(sync)
128
+ c = helper.instance_eval {committer}
129
+ c.should_receive(:insert_record).with(:right, 'scanner_records', :dummy_record)
130
+ helper.insert_record :right, 'scanner_records', :dummy_record
131
+ end
132
+
133
+ it "update_record should update the given record" do
134
+ sync = TableSync.new(Session.new, 'scanner_records')
135
+ helper = SyncHelper.new(sync)
136
+ c = helper.instance_eval {committer}
137
+ c.should_receive(:update_record).with(:right, 'scanner_records', :dummy_record, nil)
138
+ helper.update_record :right, 'scaner_records', :dummy_record
139
+ end
140
+
141
+ it "update_record should update the given record with the provided old key" do
142
+ sync = TableSync.new(Session.new, 'scanner_records')
143
+ helper = SyncHelper.new(sync)
144
+ c = helper.instance_eval {committer}
145
+ c.should_receive(:update_record).with(:right, 'scanner_records', :dummy_record, :old_key)
146
+ helper.update_record :right, 'scanner_records', :dummy_record, :old_key
147
+ end
148
+
149
+ it "delete_record should delete the given record" do
150
+ sync = TableSync.new(Session.new, 'scanner_records')
151
+ helper = SyncHelper.new(sync)
152
+ c = helper.instance_eval {committer}
153
+ c.should_receive(:delete_record).with(:right, 'scanner_records', :dummy_record)
154
+ helper.delete_record :right, 'scanner_records', :dummy_record
155
+ end
156
+
157
+ it "finalize should be delegated to the committer" do
158
+ sync = TableSync.new(Session.new, 'scanner_records')
159
+ helper = SyncHelper.new(sync)
160
+
161
+ # finalize itself should not lead to creation of committer
162
+ helper.finalize
163
+ helper.instance_eval {@committer}.should be_nil
164
+
165
+ c = helper.instance_eval {committer}
166
+ c.should_receive(:finalize).with(false)
167
+ helper.finalize(false)
168
+ end
169
+ end