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,140 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ include RR
4
+
5
+ describe TableSync do
6
+ it "sync_options should return the correct table specific sync options" do
7
+ config = deep_copy(standard_config)
8
+ old_table_specific_options = config.tables_with_options
9
+ begin
10
+ config.options = {:syncer => :bla}
11
+ config.include_tables 'scanner_records', {:syncer => :blub}
12
+ TableSync.new(Session.new(config), 'scanner_records').sync_options[:syncer] \
13
+ .should == :blub
14
+ ensure
15
+ config.instance_eval {@tables_with_options = old_table_specific_options}
16
+ end
17
+ end
18
+
19
+ it "execute_sync_hook should work if the hook is not configured" do
20
+ session = Session.new standard_config
21
+ sync = TableSync.new(session, 'scanner_records')
22
+ sync.execute_sync_hook(:before_table_sync)
23
+ end
24
+
25
+ it "execute_sync_hook should execute the given SQL command" do
26
+ config = deep_copy(standard_config)
27
+ config.add_table_options 'scanner_records', :before_table_sync => 'dummy_command'
28
+ session = Session.new config
29
+ sync = TableSync.new(session, 'scanner_records')
30
+
31
+ session.left.should_receive(:execute).with('dummy_command')
32
+ session.right.should_receive(:execute).with('dummy_command')
33
+
34
+ sync.execute_sync_hook(:before_table_sync)
35
+ end
36
+
37
+ it "execute_sync_hook should execute the given Proc" do
38
+ config = deep_copy(standard_config)
39
+ received_handler = nil
40
+ config.add_table_options 'scanner_records',
41
+ :before_table_sync => lambda {|helper| received_handler = helper}
42
+ session = Session.new config
43
+ sync = TableSync.new(session, 'scanner_records')
44
+ sync.helper = :dummy_helper
45
+
46
+ sync.execute_sync_hook(:before_table_sync)
47
+
48
+ received_handler.should == :dummy_helper
49
+ end
50
+
51
+ it "event_filtered? should return false if there is no event filter" do
52
+ session = Session.new standard_config
53
+ sync = TableSync.new(session, 'scanner_records')
54
+
55
+ sync.event_filtered?(:left, 'id' => 1).should be_false
56
+ end
57
+
58
+ it "event_filtered? should return false if event filter does not filter sync events" do
59
+ config = deep_copy(standard_config)
60
+ config.add_table_options 'scanner_records', :event_filter => Object.new
61
+ session = Session.new config
62
+ sync = TableSync.new(session, 'scanner_records')
63
+
64
+ sync.event_filtered?(:left, 'id' => 1).should be_false
65
+ end
66
+
67
+ it "event_filtered? should signal filtering (i. e. return true) if the event filter result is false" do
68
+ filter = Object.new
69
+ def filter.before_sync(table, key, helper, type, row)
70
+ false
71
+ end
72
+ config = deep_copy(standard_config)
73
+ config.add_table_options 'scanner_records', :event_filter => filter
74
+ session = Session.new config
75
+ sync = TableSync.new(session, 'scanner_records')
76
+ sync.helper = SyncHelper.new(sync)
77
+ sync.event_filtered?(:left, 'id' => 1).should be_true
78
+ end
79
+
80
+ it "event_filtered? should return false if the event filter result is true" do
81
+ filter = {}
82
+ def filter.before_sync(table, key, helper, type, row)
83
+ self[:args] = [table, key, helper, type, row]
84
+ true
85
+ end
86
+ config = deep_copy(standard_config)
87
+ config.add_table_options 'scanner_records', :event_filter => filter
88
+ session = Session.new config
89
+ sync = TableSync.new(session, 'scanner_records')
90
+ sync.helper = SyncHelper.new(sync)
91
+ sync.event_filtered?(:left, 'id' => 1, 'name' => 'bla').should be_false
92
+
93
+ # verify correct parameter assignment
94
+ filter[:args].should == ['scanner_records', {'id' => 1}, sync.helper, :left, {'id' => 1, 'name' => 'bla'}]
95
+ end
96
+
97
+ it "run should synchronize the databases" do
98
+ config = deep_copy(standard_config)
99
+ config.options[:committer] = :never_commit
100
+ config.options[:logged_sync_events] = [:all_conflicts]
101
+ before_hook_called = false
102
+ after_hook_called = false
103
+ config.options[:before_table_sync] = lambda {|helper| before_hook_called = true}
104
+ config.options[:after_table_sync] = lambda { |helper| after_hook_called = true}
105
+
106
+ filter = Object.new
107
+ def filter.before_sync(table, key, helper, type, row)
108
+ key['id'] != 6
109
+ end
110
+ config.options[:event_filter] = filter
111
+ session = Session.new(config)
112
+ begin
113
+ sync = TableSync.new(session, 'scanner_records')
114
+ sync.run
115
+
116
+ # Verify that sync events are logged
117
+ row = session.left.select_one("select * from rr_logged_events where change_key = '2' order by id")
118
+ row['change_table'].should == 'scanner_records'
119
+ row['diff_type'].should == 'conflict'
120
+ row['description'].should == 'left_wins'
121
+
122
+ # verify that the table was synchronized
123
+ left_records = session.left.select_all("select * from scanner_records where id <> 6 order by id")
124
+ right_records = session.right.select_all("select * from scanner_records where id <> 6 order by id")
125
+ left_records.should == right_records
126
+
127
+ # verify that the filtered out record was not synced
128
+ session.left.select_one("select * from scanner_records where id = 6").
129
+ should be_nil
130
+
131
+ # verify that hooks where called
132
+ before_hook_called.should be_true
133
+ after_hook_called.should be_true
134
+ ensure
135
+ Committers::NeverCommitter.rollback_current_session
136
+ session.left.execute "delete from rr_logged_events"
137
+ end
138
+ end
139
+
140
+ end
@@ -0,0 +1,47 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ include RR
4
+
5
+ describe TaskSweeper do
6
+ before(:each) do
7
+ end
8
+
9
+ it "should execute the given task" do
10
+ x = nil
11
+ TaskSweeper.timeout(1) {|sweeper| x = 1}
12
+ x.should == 1
13
+ end
14
+
15
+ it "should raise exceptions thrown by the task" do
16
+ lambda {
17
+ TaskSweeper.timeout(1) {raise "bla"}
18
+ }.should raise_error("bla")
19
+ end
20
+
21
+ it "should return if task stalls" do
22
+ start = Time.now
23
+ TaskSweeper.timeout(0.01) {sleep 10}.should be_terminated
24
+ (Time.now - start < 5).should be_true
25
+ end
26
+
27
+ it "should not return if task is active" do
28
+ start = Time.now
29
+ TaskSweeper.timeout(0.1) do |sweeper|
30
+ 10.times do
31
+ sleep 0.05
32
+ sweeper.ping
33
+ end
34
+ end.should_not be_terminated
35
+ (Time.now - start > 0.4).should be_true
36
+
37
+ end
38
+
39
+ it "should notify a stalled task about it's termination" do
40
+ terminated = false
41
+ TaskSweeper.timeout(0.01) do |sweeper|
42
+ sleep 0.05
43
+ terminated = sweeper.terminated?
44
+ end.join
45
+ terminated.should be_true
46
+ end
47
+ end
@@ -0,0 +1,83 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ include RR
4
+
5
+ describe TriggerModeSwitcher do
6
+ before(:each) do
7
+ Initializer.configuration = standard_config
8
+ end
9
+
10
+ it "initialize should save the session and initialize triggers hash" do
11
+ session = Session.new
12
+ switcher = TriggerModeSwitcher.new(session)
13
+ switcher.session.should == session
14
+ end
15
+
16
+ it "exclude_rr_activity should switch the trigger for the given table" do
17
+ switcher = TriggerModeSwitcher.new(Session.new)
18
+
19
+ switcher.should_receive(:switch_trigger_mode).with(:right, 'right1', true).once
20
+ switcher.exclude_rr_activity(:right, 'right1')
21
+
22
+ # Verify that for a given table, the trigger is not modified multiple times
23
+ switcher.exclude_rr_activity(:right, 'right1')
24
+ end
25
+
26
+ it "restore_triggers should restore the triggers" do
27
+ switcher = TriggerModeSwitcher.new(Session.new)
28
+
29
+ switcher.stub!(:switch_trigger_mode)
30
+ switcher.exclude_rr_activity :left, 'left1'
31
+ switcher.should_receive(:switch_trigger_mode).with(:left, 'left1', false).once
32
+ switcher.restore_triggers
33
+ switcher.restore_triggers # ensure the restore is only done once
34
+ end
35
+
36
+ it "switch_trigger_mode should switch the exclude_rr_activity mode as specified" do
37
+ session = nil
38
+ initializer = nil
39
+ begin
40
+ session = Session.new
41
+ initializer = ReplicationInitializer.new(session)
42
+ initializer.create_trigger(:left, 'trigger_test')
43
+
44
+ switcher = TriggerModeSwitcher.new session
45
+ switcher.switch_trigger_mode :left, 'trigger_test', true
46
+
47
+ session.left.insert_record 'trigger_test', {
48
+ 'first_id' => 1,
49
+ 'second_id' => 2,
50
+ 'name' => 'blub'
51
+ }
52
+ session.left.execute "insert into rr_running_flags values(1)"
53
+ session.left.insert_record 'trigger_test', {
54
+ 'first_id' => 1,
55
+ 'second_id' => 3,
56
+ 'name' => 'bla'
57
+ }
58
+
59
+ rows = session.left.select_all("select * from rr_pending_changes order by id")
60
+ rows.each {|row| row.delete 'id'; row.delete 'change_time'}
61
+ rows.should == [{
62
+ 'change_table' => 'trigger_test',
63
+ 'change_key' => 'first_id|1|second_id|2',
64
+ 'change_new_key' => nil,
65
+ 'change_type' => 'I'
66
+ }]
67
+ ensure
68
+ initializer.drop_trigger :left, 'trigger_test' if initializer
69
+ if session
70
+ session.left.execute 'delete from rr_running_flags'
71
+ session.left.execute 'delete from trigger_test'
72
+ session.left.execute 'delete from rr_pending_changes'
73
+ end
74
+ end
75
+ end
76
+
77
+ it "switch_trigger_mode should not switch the trigger mode if the table has no trigger" do
78
+ session = Session.new
79
+ switcher = TriggerModeSwitcher.new session
80
+ session.left.should_not_receive(:execute)
81
+ switcher.switch_trigger_mode(:left, 'scanner_records', true)
82
+ end
83
+ end
@@ -0,0 +1,721 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ include RR
4
+
5
+ describe Replicators::TwoWayReplicator do
6
+ before(:each) do
7
+ Initializer.configuration = deep_copy(standard_config)
8
+ Initializer.configuration.options = {:replicator => :two_way}
9
+ end
10
+
11
+ it "should register itself" do
12
+ Replicators::replicators[:two_way].should == Replicators::TwoWayReplicator
13
+ end
14
+
15
+ it "initialize should store the replication helper" do
16
+ rep_run = ReplicationRun.new(Session.new, TaskSweeper.new(1))
17
+ helper = ReplicationHelper.new(rep_run)
18
+ replicator = Replicators::TwoWayReplicator.new(helper)
19
+ replicator.rep_helper.should == helper
20
+ end
21
+
22
+ it "verify_option should raise descriptive errors" do
23
+ rep_run = ReplicationRun.new(Session.new, TaskSweeper.new(1))
24
+ helper = ReplicationHelper.new(rep_run)
25
+ replicator = Replicators::TwoWayReplicator.new(helper)
26
+ lambda {replicator.verify_option(nil, [:valid_value], :my_key, :my_value)}.
27
+ should raise_error(ArgumentError, ':my_value not a valid :my_key option')
28
+ lambda {replicator.verify_option(/my_spec/, [:valid_value], :my_key, :my_value)}.
29
+ should raise_error(ArgumentError, '/my_spec/: :my_value not a valid :my_key option')
30
+ end
31
+
32
+ it "initialize should throw an error if options are invalid" do
33
+ rep_run = ReplicationRun.new(Session.new, TaskSweeper.new(1))
34
+ helper = ReplicationHelper.new(rep_run)
35
+ base_options = {
36
+ :replicator => :two_way,
37
+ :left_change_handling => :ignore,
38
+ :right_change_handling => :ignore,
39
+ :replication_conflict_handling => :ignore,
40
+ :logged_replication_events => [:ignored_conflicts]
41
+ }
42
+
43
+ # Verify that correct options don't raise errors.
44
+ helper.stub!(:options).and_return(base_options)
45
+ lambda {Replicators::TwoWayReplicator.new(helper)}.should_not raise_error
46
+
47
+ # Also lambda options should not raise errors.
48
+ l = lambda {}
49
+ helper.stub!(:options).and_return(base_options.merge(
50
+ {
51
+ :left_change_handling => l,
52
+ :right_change_handling => l,
53
+ :repliction_conflict_handling => l
54
+ })
55
+ )
56
+ lambda {Replicators::TwoWayReplicator.new(helper)}.should_not raise_error
57
+
58
+ # Invalid options should raise errors
59
+ invalid_options = [
60
+ {:left_change_handling => :invalid_left_option},
61
+ {:right_change_handling => :invalid_right_option},
62
+ {:replication_conflict_handling => :invalid_conflict_option},
63
+ {:logged_replication_events => :invalid_logging_option},
64
+ ]
65
+ invalid_options.each do |options|
66
+ helper.session.configuration.stub!(:options).and_return(base_options.merge(options))
67
+ lambda {Replicators::TwoWayReplicator.new(helper)}.should raise_error(ArgumentError)
68
+ end
69
+ end
70
+
71
+ it "clear_conflicts should update the correct database with the correct action" do
72
+ Initializer.configuration.include_tables 'left_table, right_table'
73
+ session = Session.new
74
+ session.left.begin_db_transaction
75
+ session.right.begin_db_transaction
76
+ begin
77
+ rep_run = ReplicationRun.new(session, TaskSweeper.new(1))
78
+ helper = ReplicationHelper.new(rep_run)
79
+ replicator = Replicators::TwoWayReplicator.new(helper)
80
+
81
+ loaders = LoggedChangeLoaders.new(session)
82
+
83
+ left_change = LoggedChange.new loaders[:left]
84
+ left_change.table = 'left_table'
85
+ left_change.key = {'id' => '1'}
86
+ right_change = LoggedChange.new loaders[:right]
87
+ right_change.table = 'right_table'
88
+ right_change.key = {'id' => '1'}
89
+
90
+ diff = ReplicationDifference.new(loaders)
91
+ diff.changes[:left] = left_change
92
+ diff.changes[:right] = right_change
93
+
94
+
95
+ # verify that an insert is dealt correctly with
96
+ left_change.type = :insert
97
+ right_change.type = :insert
98
+
99
+ helper.should_receive(:load_record).ordered.
100
+ with(:left, 'left_table', {'id' => '1'}).
101
+ and_return(:dummy_values)
102
+ helper.should_receive(:update_record).ordered.
103
+ with(:right, 'right_table', :dummy_values, {'id' => '1'})
104
+ replicator.clear_conflict :left, diff, 1
105
+
106
+ # verify that an update is dealt correctly with
107
+ left_change.type = :delete
108
+ right_change.type = :update
109
+ right_change.new_key = {'id' => '2'}
110
+
111
+
112
+ helper.should_receive(:load_record).ordered.
113
+ with(:right, 'right_table', {'id' => '2'}).
114
+ and_return(:dummy_values)
115
+ helper.should_receive(:insert_record).ordered.
116
+ with(:left, 'left_table', :dummy_values)
117
+ replicator.clear_conflict :right, diff, 1
118
+
119
+
120
+ # verify that a delete is dealt correctly with
121
+ left_change.type = :delete
122
+ right_change.type = :update
123
+
124
+ helper.should_receive(:delete_record).ordered.
125
+ with(:right, 'right_table', {'id' => '2'})
126
+ replicator.clear_conflict :left, diff, 1
127
+ ensure
128
+ session.left.rollback_db_transaction
129
+ session.right.rollback_db_transaction
130
+ end
131
+ end
132
+
133
+ it "log_replication_outcome should log conflicts correctly" do
134
+ session = Session.new
135
+ rep_run = ReplicationRun.new(session, TaskSweeper.new(1))
136
+
137
+ loaders = LoggedChangeLoaders.new(session)
138
+
139
+ diff = ReplicationDifference.new loaders
140
+ diff.type = :conflict
141
+ diff.changes[:left] = LoggedChange.new loaders[:left]
142
+ diff.changes[:left].table = 'scanner_records'
143
+
144
+ # should only log events if so configured
145
+ helper = ReplicationHelper.new(rep_run)
146
+ replicator = Replicators::TwoWayReplicator.new(helper)
147
+ helper.should_not_receive(:log_replication_outcome)
148
+ helper.stub!(:options_for_table).and_return({:logged_replication_events => []})
149
+ replicator.log_replication_outcome :ignore, diff
150
+ helper.stub!(:options_for_table).and_return({:logged_replication_events => [:ignored_conflicts]})
151
+ replicator.log_replication_outcome :left, diff
152
+
153
+ # should log ignored conflicts correctly
154
+ helper = ReplicationHelper.new(rep_run)
155
+ replicator = Replicators::TwoWayReplicator.new(helper)
156
+ helper.should_receive(:log_replication_outcome).with(diff, 'ignored')
157
+ helper.stub!(:options_for_table).and_return({:logged_replication_events => [:ignored_conflicts]})
158
+ replicator.log_replication_outcome :ignore, diff
159
+
160
+ # should log conflicts correctly
161
+ helper = ReplicationHelper.new(rep_run)
162
+ replicator = Replicators::TwoWayReplicator.new(helper)
163
+ helper.should_receive(:log_replication_outcome).with(diff, 'left_won')
164
+ helper.stub!(:options_for_table).and_return({:logged_replication_events => [:all_conflicts]})
165
+ replicator.log_replication_outcome :left, diff
166
+ end
167
+
168
+ it "log_replication_outcome should log changes correctly" do
169
+ session = Session.new
170
+ rep_run = ReplicationRun.new(session, TaskSweeper.new(1))
171
+
172
+ loaders = LoggedChangeLoaders.new(session)
173
+
174
+ diff = ReplicationDifference.new loaders
175
+ diff.type = :left
176
+ diff.changes[:left] = LoggedChange.new loaders[:left]
177
+ diff.changes[:left].table = 'scanner_records'
178
+
179
+ # should only log events if so configured
180
+ helper = ReplicationHelper.new(rep_run)
181
+ replicator = Replicators::TwoWayReplicator.new(helper)
182
+ helper.should_not_receive(:log_replication_outcome)
183
+ helper.stub!(:options_for_table).and_return({:logged_replication_events => []})
184
+ replicator.log_replication_outcome :ignore, diff
185
+ helper.stub!(:options_for_table).and_return({:logged_replication_events => [:ignored_changes]})
186
+ replicator.log_replication_outcome :left, diff
187
+
188
+ # should log changes correctly
189
+ helper = ReplicationHelper.new(rep_run)
190
+ replicator = Replicators::TwoWayReplicator.new(helper)
191
+ helper.should_receive(:log_replication_outcome).with(diff, 'replicated')
192
+ helper.stub!(:options_for_table).and_return({:logged_replication_events => [:all_changes]})
193
+ replicator.log_replication_outcome :right, diff
194
+
195
+ # should log changes correctly
196
+ helper = ReplicationHelper.new(rep_run)
197
+ replicator = Replicators::TwoWayReplicator.new(helper)
198
+ helper.should_receive(:log_replication_outcome).with(diff, 'ignored')
199
+ helper.stub!(:options_for_table).and_return({:logged_replication_events => [:ignored_changes]})
200
+ replicator.log_replication_outcome :ignore, diff
201
+ end
202
+
203
+ it "replicate_difference should not do anything if ignore option is given" do
204
+ session = Session.new
205
+ rep_run = ReplicationRun.new(session, TaskSweeper.new(1))
206
+ helper = ReplicationHelper.new(rep_run)
207
+ replicator = Replicators::TwoWayReplicator.new(helper)
208
+ helper.stub!(:options_for_table).and_return(
209
+ {
210
+ :left_change_handling => :ignore,
211
+ :right_change_handling => :ignore,
212
+ :replication_conflict_handling => :ignore,
213
+ :logged_replication_events => [:ignored_changes, :ignored_conflicts]
214
+ }
215
+ )
216
+
217
+ loaders = LoggedChangeLoaders.new(session)
218
+
219
+ diff = ReplicationDifference.new(loaders)
220
+ diff.changes[:left] = LoggedChange.new loaders[:left]
221
+ diff.changes[:left].table = 'scanner_records'
222
+
223
+ # but logging should still happen
224
+ replicator.should_receive(:log_replication_outcome).
225
+ with(:ignore, diff).
226
+ exactly(3).times
227
+
228
+ helper.should_not_receive :insert_record
229
+ helper.should_not_receive :update_record
230
+ helper.should_not_receive :delete_record
231
+
232
+ diff.type = :conflict
233
+ replicator.replicate_difference diff
234
+ diff.type = :left
235
+ replicator.replicate_difference diff
236
+ diff.type = :right
237
+ replicator.replicate_difference diff
238
+ end
239
+
240
+ it "replicate_difference should call the provided Proc objects" do
241
+ session = Session.new
242
+ rep_run = ReplicationRun.new(session, TaskSweeper.new(1))
243
+ helper = ReplicationHelper.new(rep_run)
244
+
245
+ lambda_parameters = []
246
+ l = lambda do |rep_helper, diff|
247
+ lambda_parameters << [rep_helper, diff]
248
+ end
249
+ replicator = Replicators::TwoWayReplicator.new(helper)
250
+ helper.stub!(:options_for_table).and_return(
251
+ {
252
+ :left_change_handling => l,
253
+ :right_change_handling => l,
254
+ :replication_conflict_handling => l
255
+ }
256
+ )
257
+
258
+ loaders = LoggedChangeLoaders.new(session)
259
+
260
+ change = LoggedChange.new loaders[:left]
261
+ change.table = 'scanner_records'
262
+
263
+ d1 = ReplicationDifference.new(loaders)
264
+ d1.type = :conflict
265
+ d1.changes[:left] = change
266
+ replicator.replicate_difference d1
267
+
268
+ d2 = ReplicationDifference.new(loaders)
269
+ d2.type = :left
270
+ d2.changes[:left] = change
271
+ replicator.replicate_difference d2
272
+
273
+ d3 = ReplicationDifference.new(loaders)
274
+ d3.type = :right
275
+ d3.changes[:left] = change
276
+ replicator.replicate_difference d3
277
+
278
+ lambda_parameters.should == [
279
+ [helper, d1],
280
+ [helper, d2],
281
+ [helper, d3],
282
+ ]
283
+ end
284
+
285
+ it "replicate_difference should clear conflicts as per provided options" do
286
+ session = Session.new
287
+ rep_run = ReplicationRun.new(session, TaskSweeper.new(1))
288
+ helper = ReplicationHelper.new(rep_run)
289
+
290
+ left_change = LoggedChange.new LoggedChangeLoader.new(session, :left)
291
+ left_change.table = 'scanner_records'
292
+ right_change = LoggedChange.new LoggedChangeLoader.new(session, :right)
293
+ right_change.table = 'scanner_records'
294
+ diff = ReplicationDifference.new(session)
295
+ diff.type = :conflict
296
+ diff.changes[:left] = left_change
297
+ diff.changes[:right] = right_change
298
+
299
+ replicator = Replicators::TwoWayReplicator.new(helper)
300
+ helper.stub!(:options_for_table).and_return({:replication_conflict_handling => :left_wins})
301
+ replicator.should_receive(:clear_conflict).with(:left, diff, 1)
302
+ replicator.replicate_difference diff, 1
303
+
304
+ replicator = Replicators::TwoWayReplicator.new(helper)
305
+ helper.stub!(:options_for_table).and_return({:replication_conflict_handling => :right_wins})
306
+ replicator.should_receive(:clear_conflict).with(:right, diff, 1)
307
+ replicator.replicate_difference diff, 1
308
+
309
+ replicator = Replicators::TwoWayReplicator.new(helper)
310
+ helper.stub!(:options_for_table).and_return({:replication_conflict_handling => :later_wins})
311
+ replicator.should_receive(:clear_conflict).with(:left, diff, 1).twice
312
+ left_change.last_changed_at = 5.seconds.from_now
313
+ right_change.last_changed_at = Time.now
314
+ replicator.replicate_difference diff, 1
315
+ left_change.last_changed_at = right_change.last_changed_at = Time.now
316
+ replicator.replicate_difference diff, 1
317
+ replicator.should_receive(:clear_conflict).with(:right, diff, 1)
318
+ right_change.last_changed_at = 5.seconds.from_now
319
+ replicator.replicate_difference diff, 1
320
+
321
+ replicator = Replicators::TwoWayReplicator.new(helper)
322
+ helper.stub!(:options_for_table).and_return({:replication_conflict_handling => :earlier_wins})
323
+ replicator.should_receive(:clear_conflict).with(:left, diff, 1).twice
324
+ left_change.last_changed_at = 5.seconds.ago
325
+ right_change.last_changed_at = Time.now
326
+ replicator.replicate_difference diff, 1
327
+ left_change.last_changed_at = right_change.last_changed_at = Time.now
328
+ replicator.replicate_difference diff, 1
329
+ replicator.should_receive(:clear_conflict).with(:right, diff, 1)
330
+ right_change.last_changed_at = 5.seconds.ago
331
+ replicator.replicate_difference diff, 1
332
+ end
333
+
334
+ it "replicate_difference should replicate :left / :right changes correctly" do
335
+ Initializer.configuration.include_tables 'left_table, right_table'
336
+ session = Session.new
337
+ session.left.begin_db_transaction
338
+ session.right.begin_db_transaction
339
+ begin
340
+ rep_run = ReplicationRun.new(session, TaskSweeper.new(1))
341
+
342
+ left_change = LoggedChange.new LoggedChangeLoader.new(session, :left)
343
+ left_change.table = 'left_table'
344
+ left_change.key = {'id' => '1'}
345
+ right_change = LoggedChange.new LoggedChangeLoader.new(session, :right)
346
+ right_change.table = 'right_table'
347
+ right_change.key = {'id' => '1'}
348
+
349
+ diff = ReplicationDifference.new(session)
350
+
351
+ # verify insert behaviour
352
+ left_change.type = :insert
353
+ diff.type = :left
354
+ diff.changes[:left] = left_change
355
+ diff.changes[:right] = nil
356
+
357
+ helper = ReplicationHelper.new(rep_run)
358
+ replicator = Replicators::TwoWayReplicator.new(helper)
359
+ replicator.should_receive(:log_replication_outcome).with(:left, diff)
360
+ helper.should_receive(:load_record).with(:left, 'left_table', {'id' => '1'}).
361
+ and_return(:dummy_values)
362
+ helper.should_receive(:insert_record).with(:right, 'right_table', :dummy_values)
363
+ replicator.replicate_difference diff
364
+
365
+ # verify update behaviour
366
+ right_change.type = :update
367
+ right_change.new_key = {'id' => '2'}
368
+ diff.type = :right
369
+ diff.changes[:right] = right_change
370
+
371
+ helper = ReplicationHelper.new(rep_run)
372
+ replicator = Replicators::TwoWayReplicator.new(helper)
373
+ replicator.should_receive(:log_replication_outcome).with(:right, diff)
374
+ helper.should_receive(:load_record).with(:right, 'right_table', {'id' => '2'}).
375
+ and_return(:dummy_values)
376
+ helper.should_receive(:update_record).with(:left, 'left_table', :dummy_values, {'id' => '1'})
377
+ replicator.replicate_difference diff
378
+
379
+ # verify delete behaviour
380
+ right_change.type = :delete
381
+
382
+ helper = ReplicationHelper.new(rep_run)
383
+ replicator = Replicators::TwoWayReplicator.new(helper)
384
+ replicator.should_receive(:log_replication_outcome).with(:right, diff)
385
+ helper.should_receive(:delete_record).with(:left, 'left_table', {'id' => '1'})
386
+ replicator.replicate_difference diff
387
+ ensure
388
+ session.left.rollback_db_transaction
389
+ session.right.rollback_db_transaction
390
+ end
391
+ end
392
+
393
+ it "replicate_difference should handle inserts failing due duplicate records getting created after the original diff was loaded" do
394
+ begin
395
+ config = deep_copy(standard_config)
396
+ config.options[:committer] = :never_commit
397
+ config.options[:replication_conflict_handling] = :right_wins
398
+
399
+ session = Session.new(config)
400
+
401
+ session.left.insert_record 'extender_no_record', {
402
+ 'id' => '1',
403
+ 'name' => 'bla'
404
+ }
405
+ session.left.insert_record 'rr_pending_changes', {
406
+ 'change_table' => 'extender_no_record',
407
+ 'change_key' => 'id|1',
408
+ 'change_type' => 'I',
409
+ 'change_time' => Time.now
410
+ }
411
+
412
+
413
+ rep_run = ReplicationRun.new session, TaskSweeper.new(1)
414
+ helper = ReplicationHelper.new(rep_run)
415
+ replicator = Replicators::TwoWayReplicator.new(helper)
416
+
417
+ diff = ReplicationDifference.new LoggedChangeLoaders.new(session)
418
+ diff.load
419
+
420
+ session.right.insert_record 'extender_no_record', {
421
+ 'id' => '1',
422
+ 'name' => 'blub'
423
+ }
424
+ session.right.insert_record 'rr_pending_changes', {
425
+ 'change_table' => 'extender_no_record',
426
+ 'change_key' => 'id|1',
427
+ 'change_type' => 'I',
428
+ 'change_time' => Time.now
429
+ }
430
+ replicator.replicate_difference diff, 2
431
+
432
+ session.left.select_record(:table => "extender_no_record").should == {
433
+ 'id' => 1,
434
+ 'name' => 'blub'
435
+ }
436
+ ensure
437
+ Committers::NeverCommitter.rollback_current_session
438
+ if session
439
+ session.left.execute "delete from extender_no_record"
440
+ session.right.execute "delete from extender_no_record"
441
+ session.left.execute "delete from rr_pending_changes"
442
+ end
443
+ end
444
+ end
445
+
446
+ it "replicate_difference should handle inserts failing due the new record being deleted after the original diff was loaded" do
447
+ begin
448
+ config = deep_copy(standard_config)
449
+ config.options[:committer] = :never_commit
450
+
451
+ session = Session.new(config)
452
+
453
+ session.left.insert_record 'rr_pending_changes', {
454
+ 'change_table' => 'extender_no_record',
455
+ 'change_key' => 'id|1',
456
+ 'change_type' => 'I',
457
+ 'change_time' => Time.now
458
+ }
459
+
460
+ rep_run = ReplicationRun.new session, TaskSweeper.new(1)
461
+ helper = ReplicationHelper.new(rep_run)
462
+ replicator = Replicators::TwoWayReplicator.new(helper)
463
+
464
+ diff = ReplicationDifference.new LoggedChangeLoaders.new(session)
465
+ diff.load
466
+
467
+ session.left.insert_record 'rr_pending_changes', {
468
+ 'change_table' => 'extender_no_record',
469
+ 'change_key' => 'id|1',
470
+ 'change_type' => 'D',
471
+ 'change_time' => Time.now
472
+ }
473
+ replicator.replicate_difference diff, 2
474
+
475
+ # no rspec expectation: success is when we get till here without exception
476
+ ensure
477
+ Committers::NeverCommitter.rollback_current_session
478
+ session.left.execute "delete from rr_pending_changes" if session
479
+ end
480
+ end
481
+
482
+ it "replicate_difference should raise Exception if all replication attempts have been exceeded" do
483
+ rep_run = ReplicationRun.new Session.new, TaskSweeper.new(1)
484
+ helper = ReplicationHelper.new(rep_run)
485
+ replicator = Replicators::TwoWayReplicator.new(helper)
486
+ lambda {replicator.replicate_difference :dummy_diff, 0}.
487
+ should raise_error(Exception, "max replication attempts exceeded")
488
+ end
489
+
490
+ it "replicate_difference should handle updates rejected by the database" do
491
+ begin
492
+ config = deep_copy(standard_config)
493
+ config.options[:committer] = :never_commit
494
+ config.options[:replication_conflict_handling] = :left_wins
495
+
496
+ session = Session.new(config)
497
+ session.left.execute "delete from rr_logged_events"
498
+
499
+ session.left.insert_record 'rr_pending_changes', {
500
+ 'change_table' => 'scanner_records',
501
+ 'change_key' => 'id|1',
502
+ 'change_new_key' => 'id|2',
503
+ 'change_type' => 'U',
504
+ 'change_time' => Time.now
505
+ }
506
+
507
+ rep_run = ReplicationRun.new session, TaskSweeper.new(1)
508
+ helper = ReplicationHelper.new(rep_run)
509
+ replicator = Replicators::TwoWayReplicator.new(helper)
510
+
511
+ diff = ReplicationDifference.new LoggedChangeLoaders.new(session)
512
+ diff.load
513
+
514
+ lambda {replicator.replicate_difference diff, 1}.should raise_error(/duplicate/i)
515
+
516
+ # Verify that the transaction has not become invalid
517
+ helper.log_replication_outcome diff, "bla", "blub"
518
+
519
+ row = session.left.select_one("select * from rr_logged_events")
520
+ row['change_table'].should == 'scanner_records'
521
+ row['change_key'].should == '1'
522
+ row['description'].should == 'bla'
523
+
524
+ ensure
525
+ Committers::NeverCommitter.rollback_current_session
526
+ if session
527
+ session.left.execute "delete from rr_pending_changes"
528
+ session.left.execute "delete from rr_logged_events"
529
+ end
530
+ end
531
+ end
532
+
533
+ it "replicate_difference should handle deletes rejected by the database" do
534
+ begin
535
+ config = deep_copy(standard_config)
536
+ config.options[:committer] = :never_commit
537
+ config.options[:replication_conflict_handling] = :left_wins
538
+
539
+ session = Session.new(config)
540
+
541
+ session.left.select_all("select * from rr_logged_events").should == []
542
+
543
+ session.left.insert_record 'rr_pending_changes', {
544
+ 'change_table' => 'referenced_table',
545
+ 'change_key' => 'first_id|1|second_id|2',
546
+ 'change_new_key' => nil,
547
+ 'change_type' => 'D',
548
+ 'change_time' => Time.now
549
+ }
550
+
551
+ rep_run = ReplicationRun.new session, TaskSweeper.new(1)
552
+ helper = ReplicationHelper.new(rep_run)
553
+ replicator = Replicators::TwoWayReplicator.new(helper)
554
+
555
+ diff = ReplicationDifference.new LoggedChangeLoaders.new(session)
556
+ diff.load
557
+
558
+ lambda {replicator.replicate_difference diff, 1}.should raise_error(/referencing_table_fkey/)
559
+
560
+ # Verify that the transaction has not become invalid
561
+ helper.log_replication_outcome diff, "bla", "blub"
562
+
563
+ row = session.left.select_one("select * from rr_logged_events")
564
+ row['change_table'].should == 'referenced_table'
565
+ row['change_key'].should =~ /first_id.*1.*second_id.*2/
566
+ row['description'].should == 'bla'
567
+
568
+ ensure
569
+ Committers::NeverCommitter.rollback_current_session
570
+ if session
571
+ session.left.execute "delete from rr_pending_changes"
572
+ session.left.execute "delete from rr_logged_events"
573
+ end
574
+ end
575
+ end
576
+
577
+ it "replicate_difference should handle deletes failing due to the target record vanishing" do
578
+ begin
579
+ config = deep_copy(standard_config)
580
+ config.options[:committer] = :never_commit
581
+ config.options[:replication_conflict_handling] = :left_wins
582
+
583
+ session = Session.new(config)
584
+
585
+ session.left.insert_record 'rr_pending_changes', {
586
+ 'change_table' => 'scanner_records',
587
+ 'change_key' => 'id|3',
588
+ 'change_new_key' => nil,
589
+ 'change_type' => 'D',
590
+ 'change_time' => Time.now
591
+ }
592
+
593
+ rep_run = ReplicationRun.new session, TaskSweeper.new(1)
594
+ helper = ReplicationHelper.new(rep_run)
595
+ replicator = Replicators::TwoWayReplicator.new(helper)
596
+
597
+ diff = ReplicationDifference.new LoggedChangeLoaders.new(session)
598
+ diff.load
599
+
600
+ session.right.insert_record 'rr_pending_changes', {
601
+ 'change_table' => 'scanner_records',
602
+ 'change_key' => 'id|3',
603
+ 'change_new_key' => 'id|4',
604
+ 'change_type' => 'U',
605
+ 'change_time' => Time.now
606
+ }
607
+
608
+ replicator.replicate_difference diff, 2
609
+
610
+ session.right.select_one("select * from scanner_records where id = 4").
611
+ should be_nil
612
+ ensure
613
+ Committers::NeverCommitter.rollback_current_session
614
+ if session
615
+ session.left.execute "delete from rr_pending_changes"
616
+ session.left.execute "delete from rr_logged_events"
617
+ end
618
+ end
619
+ end
620
+
621
+ it "replicate_difference should handle updates failing due to the source record being deleted after the original diff was loaded" do
622
+ begin
623
+ config = deep_copy(standard_config)
624
+ config.options[:committer] = :never_commit
625
+ config.options[:replication_conflict_handling] = :left_wins
626
+
627
+ session = Session.new(config)
628
+
629
+ session.left.insert_record 'extender_no_record', {
630
+ 'id' => '2',
631
+ 'name' => 'bla'
632
+ }
633
+ session.right.insert_record 'extender_no_record', {
634
+ 'id' => '2',
635
+ 'name' => 'blub'
636
+ }
637
+ session.left.insert_record 'rr_pending_changes', {
638
+ 'change_table' => 'extender_no_record',
639
+ 'change_key' => 'id|1',
640
+ 'change_new_key' => 'id|2',
641
+ 'change_type' => 'U',
642
+ 'change_time' => Time.now
643
+ }
644
+
645
+ rep_run = ReplicationRun.new session, TaskSweeper.new(1)
646
+ helper = ReplicationHelper.new(rep_run)
647
+ replicator = Replicators::TwoWayReplicator.new(helper)
648
+
649
+ diff = ReplicationDifference.new LoggedChangeLoaders.new(session)
650
+ diff.load
651
+
652
+ session.left.delete_record 'extender_no_record', {'id' => '2'}
653
+
654
+ session.left.insert_record 'rr_pending_changes', {
655
+ 'change_table' => 'extender_no_record',
656
+ 'change_key' => 'id|2',
657
+ 'change_type' => 'D',
658
+ 'change_time' => Time.now
659
+ }
660
+ replicator.replicate_difference diff, 2
661
+
662
+ session.right.select_one("select * from extender_no_record").should be_nil
663
+ ensure
664
+ Committers::NeverCommitter.rollback_current_session
665
+ if session
666
+ session.left.execute "delete from extender_no_record"
667
+ session.right.execute "delete from extender_no_record"
668
+ session.left.execute "delete from rr_pending_changes"
669
+ end
670
+ end
671
+ end
672
+
673
+ it "replicate_difference should handle updates failing due to the target record being deleted after the original diff was loaded" do
674
+ begin
675
+ config = deep_copy(standard_config)
676
+ config.options[:committer] = :never_commit
677
+ config.options[:replication_conflict_handling] = :left_wins
678
+
679
+ session = Session.new(config)
680
+
681
+ session.left.insert_record 'extender_no_record', {
682
+ 'id' => '2',
683
+ 'name' => 'bla'
684
+ }
685
+ session.left.insert_record 'rr_pending_changes', {
686
+ 'change_table' => 'extender_no_record',
687
+ 'change_key' => 'id|1',
688
+ 'change_new_key' => 'id|2',
689
+ 'change_type' => 'U',
690
+ 'change_time' => Time.now
691
+ }
692
+
693
+ rep_run = ReplicationRun.new session, TaskSweeper.new(1)
694
+ helper = ReplicationHelper.new(rep_run)
695
+ replicator = Replicators::TwoWayReplicator.new(helper)
696
+
697
+ diff = ReplicationDifference.new LoggedChangeLoaders.new(session)
698
+ diff.load
699
+
700
+ session.right.insert_record 'rr_pending_changes', {
701
+ 'change_table' => 'extender_no_record',
702
+ 'change_key' => 'id|1',
703
+ 'change_type' => 'D',
704
+ 'change_time' => Time.now
705
+ }
706
+ replicator.replicate_difference diff, 2
707
+
708
+ session.right.select_record(:table => "extender_no_record").should == {
709
+ 'id' => 2,
710
+ 'name' => 'bla'
711
+ }
712
+ ensure
713
+ Committers::NeverCommitter.rollback_current_session
714
+ if session
715
+ session.left.execute "delete from extender_no_record"
716
+ session.right.execute "delete from extender_no_record"
717
+ session.left.execute "delete from rr_pending_changes"
718
+ end
719
+ end
720
+ end
721
+ end