andyjeffries-rubyrep 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
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