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,56 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ include RR
4
+
5
+ describe ProxyCursor do
6
+ before(:each) do
7
+ Initializer.configuration = proxied_config
8
+ end
9
+
10
+ it "initialize should store session and table and cache the primary keys of table" do
11
+ connection = create_mock_proxy_connection 'dummy_table', ['dummy_key']
12
+
13
+ cursor = ProxyCursor.new connection, 'dummy_table'
14
+
15
+ cursor.connection.should == connection
16
+ cursor.table.should == 'dummy_table'
17
+ cursor.primary_key_names.should == ['dummy_key']
18
+ end
19
+
20
+ it "prepare_fetch should initiate the query and wrap it for type casting" do
21
+ connection = ProxyConnection.new Initializer.configuration.left
22
+
23
+ cursor = ProxyCursor.new(connection, 'scanner_records')
24
+ cursor.prepare_fetch
25
+ cursor.cursor.should be_an_instance_of(TypeCastingCursor)
26
+ cursor.cursor.next_row.should == {'id' => 1, 'name' => 'Alice - exists in both databases'}
27
+ end
28
+
29
+ it "prepare_fetch called with option :row_keys should initiate the correct query" do
30
+ # Note: I am testing row_keys exclusively to make sure that this type of
31
+ # sub query will work correctly on all supported databases
32
+ connection = ProxyConnection.new Initializer.configuration.left
33
+
34
+ cursor = ProxyCursor.new(connection, 'extender_combined_key')
35
+ cursor.prepare_fetch :row_keys => [
36
+ {'first_id' => 1, 'second_id' => 1},
37
+ {'first_id' => 1, 'second_id' => 2}
38
+ ]
39
+ cursor.cursor.next_row.should == {'first_id' => 1, 'second_id' => 1, 'name' => 'aa'}
40
+ cursor.cursor.next_row.should == {'first_id' => 1, 'second_id' => 2, 'name' => 'ab'}
41
+ cursor.cursor.next?.should == false
42
+ end
43
+
44
+
45
+ it "destroy should clear and nil the cursor" do
46
+ connection = create_mock_proxy_connection 'dummy_table', ['dummy_key']
47
+ cursor = ProxyCursor.new connection, 'dummy_table'
48
+
49
+ table_cursor = mock("DBCursor")
50
+ table_cursor.should_receive(:clear)
51
+ cursor.cursor = table_cursor
52
+
53
+ cursor.destroy
54
+ cursor.cursor.should be_nil
55
+ end
56
+ end
@@ -0,0 +1,66 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ include RR
4
+
5
+ describe ProxyRowCursor do
6
+ before(:each) do
7
+ Initializer.configuration = standard_config
8
+ end
9
+
10
+ it "initialize should super to ProxyCursor" do
11
+ session = create_mock_proxy_connection 'dummy_table', ['dummy_id']
12
+ cursor = ProxyRowCursor.new session, 'dummy_table'
13
+ cursor.table.should == 'dummy_table'
14
+ end
15
+
16
+ it "next? should delegate to the DB cursor" do
17
+ session = create_mock_proxy_connection 'dummy_table', ['dummy_id']
18
+ cursor = ProxyRowCursor.new session, 'dummy_table'
19
+
20
+ table_cursor = mock("DBCursor")
21
+ table_cursor.should_receive(:next?).and_return(true)
22
+ cursor.cursor = table_cursor
23
+
24
+ cursor.next?.should == true
25
+ end
26
+
27
+ it "next_row should return the next row in the cursor" do
28
+ session = create_mock_proxy_connection 'dummy_table', ['dummy_id']
29
+ cursor = ProxyRowCursor.new session, 'dummy_table'
30
+
31
+ table_cursor = mock("DBCursor")
32
+ table_cursor.should_receive(:next_row).and_return(:dummy_row)
33
+ cursor.cursor = table_cursor
34
+
35
+ cursor.next_row.should == :dummy_row
36
+ end
37
+
38
+ it "next_row_keys_and_checksum should store the found row under current_row" do
39
+ session = create_mock_proxy_connection 'dummy_table', ['dummy_id']
40
+ cursor = ProxyRowCursor.new session, 'dummy_table'
41
+
42
+ table_cursor = mock("DBCursor")
43
+ table_cursor.should_receive(:next_row).and_return('dummy_id' => 'dummy_value')
44
+
45
+ cursor.cursor = table_cursor
46
+ cursor.next_row_keys_and_checksum
47
+ cursor.current_row.should == {'dummy_id' => 'dummy_value'}
48
+ end
49
+
50
+ it "next_row_keys_and_checksum should returns the primary_keys and checksum of the found row" do
51
+ session = ProxyConnection.new proxied_config.left
52
+
53
+ cursor = ProxyRowCursor.new session, 'scanner_records'
54
+ cursor.prepare_fetch
55
+
56
+ keys, checksum = cursor.next_row_keys_and_checksum
57
+
58
+ expected_checksum = Digest::SHA1.hexdigest(
59
+ Marshal.dump('id' => 1, 'name' => 'Alice - exists in both databases')
60
+ )
61
+
62
+ keys.should == {'id' => 1}
63
+ checksum.should == expected_checksum
64
+ end
65
+
66
+ end
@@ -0,0 +1,70 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ include RR
4
+
5
+ describe ProxyRunner do
6
+ before(:each) do
7
+ DRb.stub!(:start_service)
8
+ DRb.thread.stub!(:join)
9
+ $stderr.stub!(:puts)
10
+ end
11
+
12
+ it "get_options should return options as nil and status as 1 if command line parameters are unknown" do
13
+ # also verify that an error message is printed
14
+ $stderr.should_receive(:puts).any_number_of_times
15
+ options, status = ProxyRunner.new.get_options ["--nonsense"]
16
+ options.should == nil
17
+ status.should == 1
18
+ end
19
+
20
+ it "get_options should return options as nil and status as 0 if command line includes '--help'" do
21
+ # also verify that the help message is printed
22
+ $stderr.should_receive(:puts)
23
+ options, status = ProxyRunner.new.get_options ["--help"]
24
+ options.should == nil
25
+ status.should == 0
26
+ end
27
+
28
+ it "get_options should return the default options if none were given on the command line" do
29
+ options, status = ProxyRunner.new.get_options []
30
+ options.should == ProxyRunner::DEFAULT_OPTIONS
31
+ status.should == 0
32
+ end
33
+
34
+ it "get_options should return :host and :port options as per given command line" do
35
+ options, status = ProxyRunner.new.get_options ["--host", "127.0.0.1", "--port", "1234"]
36
+ options.should == {:host => '127.0.0.1', :port => 1234}
37
+ status.should == 0
38
+ end
39
+
40
+ it "construct_url should create the correct druby URL" do
41
+ ProxyRunner.new.build_url(:host => '127.0.0.1', :port => '1234').should == "druby://127.0.0.1:1234"
42
+ end
43
+
44
+ it "start_server should create a DatabaseProxy and start the DRB server" do
45
+ DatabaseProxy.should_receive(:new)
46
+ DRb.should_receive(:start_service,"druby://127.0.0.1:1234")
47
+ DRb.stub!(:thread).and_return(Object.new)
48
+ DRb.thread.should_receive(:join)
49
+ ProxyRunner.new.start_server("druby://127.0.0.1:1234")
50
+ end
51
+
52
+ it "run should not start a server if the command line is invalid" do
53
+ DRb.should_not_receive(:start_service)
54
+ DRb.stub!(:thread).and_return(Object.new)
55
+ DRb.thread.should_not_receive(:join)
56
+ ProxyRunner.run("--nonsense")
57
+ end
58
+
59
+ it "run should start a server if the command line is correct" do
60
+ DRb.should_receive(:start_service)
61
+ DRb.stub!(:thread).and_return(Object.new)
62
+ DRb.thread.should_receive(:join)
63
+ ProxyRunner.run(["--port=1234"])
64
+ end
65
+
66
+ it "should register itself with CommandRunner" do
67
+ CommandRunner.commands['proxy'][:command].should == ProxyRunner
68
+ CommandRunner.commands['proxy'][:description].should be_an_instance_of(String)
69
+ end
70
+ end
@@ -0,0 +1,161 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ include RR
4
+
5
+ describe ReplicationDifference do
6
+ before(:each) do
7
+ Initializer.configuration = standard_config
8
+ end
9
+
10
+ it "initialize should store the loaders" do
11
+ session = Session.new
12
+ loaders = LoggedChangeLoaders.new session
13
+ diff = ReplicationDifference.new loaders
14
+ diff.loaders.should == loaders
15
+ end
16
+
17
+ it "loaded? should return true if a difference was loaded" do
18
+ diff = ReplicationDifference.new LoggedChangeLoaders.new(Session.new)
19
+ diff.should_not be_loaded
20
+ diff.loaded = true
21
+ diff.should be_loaded
22
+ end
23
+
24
+ it "load should leave the instance unloaded if no changes are available" do
25
+ diff = ReplicationDifference.new LoggedChangeLoaders.new(Session.new)
26
+ diff.load
27
+ diff.should_not be_loaded
28
+ end
29
+
30
+ it "load should load left differences successfully" do
31
+ session = Session.new
32
+ session.left.begin_db_transaction
33
+ begin
34
+ session.left.insert_record 'rr_pending_changes', {
35
+ 'change_table' => 'scanner_records',
36
+ 'change_key' => 'id|1',
37
+ 'change_type' => 'I',
38
+ 'change_time' => Time.now
39
+ }
40
+ diff = ReplicationDifference.new LoggedChangeLoaders.new(session)
41
+ diff.load
42
+
43
+ diff.should be_loaded
44
+ diff.type.should == :left
45
+ diff.changes[:left].key.should == {'id' => '1'}
46
+ ensure
47
+ session.left.rollback_db_transaction
48
+ end
49
+ end
50
+
51
+ it "load should load right differences successfully" do
52
+ session = Session.new
53
+ session.right.begin_db_transaction
54
+ begin
55
+ session.right.insert_record 'rr_pending_changes', {
56
+ 'change_table' => 'scanner_records',
57
+ 'change_key' => 'id|1',
58
+ 'change_type' => 'D',
59
+ 'change_time' => Time.now
60
+ }
61
+ diff = ReplicationDifference.new LoggedChangeLoaders.new(session)
62
+ diff.load
63
+
64
+ diff.should be_loaded
65
+ diff.type.should == :right
66
+ diff.changes[:right].key.should == {'id' => '1'}
67
+ ensure
68
+ session.right.rollback_db_transaction
69
+ end
70
+ end
71
+
72
+ it "load should load conflict differences successfully" do
73
+ config = deep_copy(standard_config)
74
+ config.included_table_specs.clear
75
+ config.include_tables "table_with_manual_key, extender_without_key"
76
+
77
+ session = Session.new config
78
+ session.left.begin_db_transaction
79
+ session.right.begin_db_transaction
80
+ begin
81
+ session.left.insert_record 'rr_pending_changes', {
82
+ 'change_table' => 'dummy_table',
83
+ 'change_key' => 'id|2',
84
+ 'change_type' => 'I',
85
+ 'change_time' => Time.now
86
+ }
87
+ session.left.insert_record 'rr_pending_changes', {
88
+ 'change_table' => 'table_with_manual_key',
89
+ 'change_key' => 'id|1',
90
+ 'change_new_key' => 'id|1',
91
+ 'change_type' => 'U',
92
+ 'change_time' => 5.seconds.from_now
93
+ }
94
+ session.right.insert_record 'rr_pending_changes', {
95
+ 'change_table' => 'extender_without_key',
96
+ 'change_key' => 'id|1',
97
+ 'change_type' => 'D',
98
+ 'change_time' => 5.seconds.ago
99
+ }
100
+ diff = ReplicationDifference.new LoggedChangeLoaders.new(session)
101
+ diff.load
102
+
103
+ diff.should be_loaded
104
+ diff.type.should == :conflict
105
+ diff.changes[:left].type.should == :update
106
+ diff.changes[:left].table.should == 'table_with_manual_key'
107
+ diff.changes[:left].key.should == {'id' => '1'}
108
+ diff.changes[:right].type.should == :delete
109
+ diff.changes[:right].table.should == 'extender_without_key'
110
+ diff.changes[:right].key.should == {'id' => '1'}
111
+ ensure
112
+ session.left.rollback_db_transaction
113
+ session.right.rollback_db_transaction
114
+ end
115
+ end
116
+
117
+ it "amend should amend the replication difference with new found changes" do
118
+ session = Session.new
119
+ session.left.begin_db_transaction
120
+ session.right.begin_db_transaction
121
+ begin
122
+ session.right.insert_record 'rr_pending_changes', {
123
+ 'change_table' => 'scanner_records',
124
+ 'change_key' => 'id|1',
125
+ 'change_type' => 'I',
126
+ 'change_time' => Time.now
127
+ }
128
+ diff = ReplicationDifference.new LoggedChangeLoaders.new(session)
129
+ diff.load
130
+
131
+ diff.should be_loaded
132
+ diff.type.should == :right
133
+ diff.changes[:right].key.should == {'id' => '1'}
134
+
135
+ # if there are no changes, the diff should still be the same
136
+ diff.amend
137
+ diff.type.should == :right
138
+ diff.changes[:right].key.should == {'id' => '1'}
139
+
140
+ # should recognize new changes
141
+ session.left.insert_record 'rr_pending_changes', {
142
+ 'change_table' => 'scanner_records',
143
+ 'change_key' => 'id|1',
144
+ 'change_type' => 'D',
145
+ 'change_time' => Time.now
146
+ }
147
+ diff.amend
148
+ diff.type.should == :conflict
149
+ diff.changes[:left].key.should == {'id' => '1'}
150
+ diff.changes[:right].key.should == {'id' => '1'}
151
+ ensure
152
+ session.right.rollback_db_transaction
153
+ session.left.rollback_db_transaction
154
+ end
155
+ end
156
+
157
+ it "to_yaml should blank out session" do
158
+ diff = ReplicationDifference.new :dummy_session
159
+ diff.to_yaml.should_not =~ /session/
160
+ end
161
+ end
@@ -0,0 +1,367 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+ require 'yaml'
3
+
4
+ include RR
5
+
6
+ # All ReplicationExtenders need to pass this spec
7
+ describe "ReplicationExtender", :shared => true do
8
+ before(:each) do
9
+ end
10
+
11
+ it "create_replication_trigger created triggers should log data changes" do
12
+ session = nil
13
+ begin
14
+ session = Session.new
15
+ session.left.begin_db_transaction
16
+ params = {
17
+ :trigger_name => 'rr_trigger_test',
18
+ :table => 'trigger_test',
19
+ :keys => ['first_id', 'second_id'],
20
+ :log_table => 'rr_pending_changes',
21
+ :key_sep => '|',
22
+ :exclude_rr_activity => false,
23
+ }
24
+ session.left.create_replication_trigger params
25
+
26
+ change_start = Time.now
27
+
28
+ session.left.insert_record 'trigger_test', {
29
+ 'first_id' => 1,
30
+ 'second_id' => 2,
31
+ 'name' => 'bla'
32
+ }
33
+ session.left.execute "update trigger_test set second_id = 9 where first_id = 1 and second_id = 2"
34
+ session.left.delete_record 'trigger_test', {
35
+ 'first_id' => 1,
36
+ 'second_id' => 9,
37
+ }
38
+
39
+ rows = session.left.connection.select_all("select * from rr_pending_changes order by id")
40
+
41
+ # Verify that the timestamps are created correctly
42
+ rows.each do |row|
43
+ Time.parse(row['change_time']).to_i >= change_start.to_i
44
+ Time.parse(row['change_time']).to_i <= Time.now.to_i
45
+ end
46
+
47
+ rows.each {|row| row.delete 'id'; row.delete 'change_time'}
48
+ rows.should == [
49
+ {'change_table' => 'trigger_test', 'change_key' => 'first_id|1|second_id|2', 'change_new_key' => nil, 'change_type' => 'I'},
50
+ {'change_table' => 'trigger_test', 'change_key' => 'first_id|1|second_id|2', 'change_new_key' => 'first_id|1|second_id|9', 'change_type' => 'U'},
51
+ {'change_table' => 'trigger_test', 'change_key' => 'first_id|1|second_id|9', 'change_new_key' => nil, 'change_type' => 'D'},
52
+ ]
53
+ ensure
54
+ session.left.execute 'delete from trigger_test' if session
55
+ session.left.execute 'delete from rr_pending_changes' if session
56
+ session.left.rollback_db_transaction if session
57
+ end
58
+ end
59
+
60
+ it "created triggers should not log rubyrep initiated changes if :exclude_rubyrep_activity is true" do
61
+ session = nil
62
+ begin
63
+ session = Session.new
64
+ session.left.begin_db_transaction
65
+ params = {
66
+ :trigger_name => 'rr_trigger_test',
67
+ :table => 'trigger_test',
68
+ :keys => ['first_id', 'second_id'],
69
+ :log_table => 'rr_pending_changes',
70
+ :key_sep => '|',
71
+ :exclude_rr_activity => true,
72
+ :activity_table => "rr_running_flags",
73
+ }
74
+ session.left.create_replication_trigger params
75
+
76
+ session.left.insert_record 'rr_running_flags', {
77
+ 'active' => 1
78
+ }
79
+ session.left.insert_record 'trigger_test', {
80
+ 'first_id' => 1,
81
+ 'second_id' => 2,
82
+ 'name' => 'bla'
83
+ }
84
+ session.left.connection.execute('delete from rr_running_flags')
85
+ session.left.insert_record 'trigger_test', {
86
+ 'first_id' => 1,
87
+ 'second_id' => 3,
88
+ 'name' => 'bla'
89
+ }
90
+
91
+ rows = session.left.connection.select_all("select * from rr_pending_changes order by id")
92
+ rows.each {|row| row.delete 'id'; row.delete 'change_time'}
93
+ rows.should == [{
94
+ 'change_table' => 'trigger_test',
95
+ 'change_key' => 'first_id|1|second_id|3',
96
+ 'change_new_key' => nil,
97
+ 'change_type' => 'I'
98
+ }]
99
+ ensure
100
+ session.left.execute 'delete from trigger_test' if session
101
+ session.left.execute 'delete from rr_pending_changes' if session
102
+ session.left.rollback_db_transaction if session
103
+ end
104
+ end
105
+
106
+ it "created triggers should work with tables having non-combined primary keys" do
107
+ session = nil
108
+ begin
109
+ session = Session.new
110
+ session.left.begin_db_transaction
111
+ params = {
112
+ :trigger_name => 'rr_extender_no_record',
113
+ :table => 'extender_no_record',
114
+ :keys => ['id'],
115
+ :log_table => 'rr_pending_changes',
116
+ :key_sep => '|',
117
+ }
118
+ session.left.create_replication_trigger params
119
+ session.left.insert_record 'extender_no_record', {
120
+ 'id' => 9,
121
+ 'name' => 'bla'
122
+ }
123
+ rows = session.left.connection.select_all("select * from rr_pending_changes order by id")
124
+ rows.each {|row| row.delete 'id'; row.delete 'change_time'}
125
+ rows.should == [{
126
+ 'change_table' => 'extender_no_record',
127
+ 'change_key' => 'id|9',
128
+ 'change_new_key' => nil,
129
+ 'change_type' => 'I'
130
+ }]
131
+ ensure
132
+ if session
133
+ session.left.execute 'delete from extender_no_record'
134
+ session.left.execute 'delete from rr_pending_changes'
135
+ session.left.drop_replication_trigger('rr_extender_no_record', 'extender_no_record')
136
+ session.left.rollback_db_transaction
137
+ end
138
+ end
139
+ end
140
+
141
+ it "created triggers should work with primary keys holding multi-byte text values" do
142
+ session = nil
143
+ begin
144
+ session = Session.new
145
+ session.left.begin_db_transaction
146
+ params = {
147
+ :trigger_name => 'rr_scanner_text_key',
148
+ :table => 'scanner_text_key',
149
+ :keys => ['text_id'],
150
+ :log_table => 'rr_pending_changes',
151
+ :key_sep => '|',
152
+ }
153
+ session.left.create_replication_trigger params
154
+ session.left.insert_record 'scanner_text_key', {
155
+ 'text_id' => 'よろしくお願(ねが)いします yoroshiku onegai shimasu: I humbly ask for your favor.',
156
+ 'name' => 'bla'
157
+ }
158
+ rows = session.left.connection.select_all("select * from rr_pending_changes order by id")
159
+ rows.each {|row| row.delete 'id'; row.delete 'change_time'}
160
+ rows.should == [{
161
+ 'change_table' => 'scanner_text_key',
162
+ 'change_key' => 'text_id|よろしくお願(ねが)いします yoroshiku onegai shimasu: I humbly ask for your favor.',
163
+ 'change_new_key' => nil,
164
+ 'change_type' => 'I'
165
+ }]
166
+ found_key = rows[0]['change_key'].sub(/^text_id\|/, '')
167
+ session.left.
168
+ select_one("select * from scanner_text_key where text_id = '#{found_key}'").
169
+ should_not be_nil
170
+ ensure
171
+ if session
172
+ session.left.execute "delete from scanner_text_key where text_id = 'よろしくお願(ねが)いします yoroshiku onegai shimasu: I humbly ask for your favor.'"
173
+ session.left.execute 'delete from rr_pending_changes'
174
+ session.left.drop_replication_trigger('rr_scanner_text_key', 'scanner_text_key')
175
+ session.left.rollback_db_transaction
176
+ end
177
+ end
178
+ end
179
+
180
+ it "replication_trigger_exists? and drop_replication_trigger should work correctly" do
181
+ session = nil
182
+ begin
183
+ session = Session.new
184
+ if session.left.replication_trigger_exists?('rr_trigger_test', 'trigger_test')
185
+ session.left.drop_replication_trigger('rr_trigger_test', 'trigger_test')
186
+ end
187
+ session.left.begin_db_transaction
188
+ params = {
189
+ :trigger_name => 'rr_trigger_test',
190
+ :table => 'trigger_test',
191
+ :keys => ['first_id'],
192
+ :log_table => 'rr_pending_changes',
193
+ :key_sep => '|',
194
+ }
195
+ session.left.create_replication_trigger params
196
+
197
+ session.left.replication_trigger_exists?('rr_trigger_test', 'trigger_test').
198
+ should be_true
199
+ session.left.drop_replication_trigger('rr_trigger_test', 'trigger_test')
200
+ session.left.replication_trigger_exists?('rr_trigger_test', 'trigger_test').
201
+ should be_false
202
+ ensure
203
+ session.left.rollback_db_transaction if session
204
+ end
205
+ end
206
+
207
+ it "sequence_values should return an empty hash if table has no sequences" do
208
+ session = Session.new
209
+ session.left.sequence_values('rr', 'scanner_text_key').
210
+ should == {}
211
+ end
212
+
213
+ it "sequence_values should return the correct sequence settings" do
214
+ session = nil
215
+ begin
216
+ session = Session.new
217
+ session.left.begin_db_transaction
218
+ session.left.execute 'delete from sequence_test'
219
+ left_sequence_values = session.left.sequence_values 'rr', 'sequence_test'
220
+ right_sequence_values = session.right.sequence_values 'rr', 'sequence_test'
221
+ session.left.update_sequences \
222
+ 'rr', 'sequence_test', 5, 2,
223
+ left_sequence_values, right_sequence_values, 5
224
+ sequence_value = session.left.sequence_values('rr', 'sequence_test').values[0]
225
+ sequence_value[:increment].should == 5
226
+ (sequence_value[:value] % 5).should == 2
227
+ ensure
228
+ session.left.clear_sequence_setup 'rr', 'sequence_test' if session
229
+ session.left.execute "delete from sequence_test" if session
230
+ session.left.rollback_db_transaction if session
231
+ end
232
+ end
233
+
234
+ it "update_sequences should ensure that a table's auto generated ID values have the correct increment and offset" do
235
+ session = nil
236
+ begin
237
+ session = Session.new
238
+ session.left.begin_db_transaction
239
+
240
+ # Note:
241
+ # Calling ensure_sequence_setup twice with different values to ensure that
242
+ # it is actually does something.
243
+
244
+ session.left.execute 'delete from sequence_test'
245
+ left_sequence_values = session.left.sequence_values 'rr', 'sequence_test'
246
+ right_sequence_values = session.right.sequence_values 'rr', 'sequence_test'
247
+ session.left.update_sequences \
248
+ 'rr', 'sequence_test', 1, 0,
249
+ left_sequence_values, right_sequence_values, 5
250
+ id1, id2 = get_example_sequence_values(session)
251
+ (id2 - id1).should == 1
252
+
253
+ left_sequence_values = session.left.sequence_values 'rr', 'sequence_test'
254
+ right_sequence_values = session.right.sequence_values 'rr', 'sequence_test'
255
+ session.left.update_sequences \
256
+ 'rr', 'sequence_test', 5, 2,
257
+ left_sequence_values, right_sequence_values, 5
258
+ id1, id2 = get_example_sequence_values(session)
259
+ (id2 - id1).should == 5
260
+ (id1 % 5).should == 2
261
+ ensure
262
+ session.left.clear_sequence_setup 'rr', 'sequence_test' if session
263
+ session.left.execute "delete from sequence_test" if session
264
+ session.left.rollback_db_transaction if session
265
+ end
266
+ end
267
+
268
+ it "update_sequences shoud set the sequence up correctly if the table is not empty" do
269
+ session = nil
270
+ begin
271
+ session = Session.new
272
+ session.left.begin_db_transaction
273
+ session.left.execute 'delete from sequence_test'
274
+ session.left.insert_record 'sequence_test', { 'name' => 'whatever' }
275
+ left_sequence_values = session.left.sequence_values 'rr', 'sequence_test'
276
+ right_sequence_values = session.right.sequence_values 'rr', 'sequence_test'
277
+ session.left.update_sequences \
278
+ 'rr', 'sequence_test', 2, 0,
279
+ left_sequence_values, right_sequence_values, 5
280
+ id1, id2 = get_example_sequence_values(session)
281
+ (id2 - id1).should == 2
282
+ ensure
283
+ session.left.clear_sequence_setup 'rr', 'sequence_test' if session
284
+ session.left.execute "delete from sequence_test" if session
285
+ session.left.rollback_db_transaction if session
286
+ end
287
+ end
288
+
289
+ it "update_sequences shoud set the sequence up correctly if the other table is already set up correctly" do
290
+ session = nil
291
+ begin
292
+ session = Session.new
293
+ session.left.begin_db_transaction
294
+ session.right.begin_db_transaction
295
+ session.left.execute 'delete from sequence_test'
296
+ session.left.insert_record 'sequence_test', { 'name' => 'whatever' }
297
+ left_sequence_values = session.left.sequence_values 'rr', 'sequence_test'
298
+ right_sequence_values = session.right.sequence_values 'rr', 'sequence_test'
299
+
300
+ # setup right table sequence
301
+ left_sequence_values = session.left.sequence_values 'rr', 'sequence_test'
302
+ right_sequence_values = session.right.sequence_values 'rr', 'sequence_test'
303
+ session.right.update_sequences \
304
+ 'rr', 'sequence_test', 2, 1,
305
+ left_sequence_values, right_sequence_values, 5
306
+
307
+ # now setup left table sequence and verify result
308
+ left_sequence_values = session.left.sequence_values 'rr', 'sequence_test'
309
+ right_sequence_values = session.right.sequence_values 'rr', 'sequence_test'
310
+ session.left.update_sequences \
311
+ 'rr', 'sequence_test', 2, 0,
312
+ left_sequence_values, right_sequence_values, 5
313
+ id1, id2 = get_example_sequence_values(session)
314
+ (id2 - id1).should == 2
315
+ ensure
316
+ session.left.clear_sequence_setup 'rr', 'sequence_test' if session
317
+ session.right.clear_sequence_setup 'rr', 'sequence_test' if session
318
+ session.left.execute "delete from sequence_test" if session
319
+ session.left.rollback_db_transaction if session
320
+ session.right.rollback_db_transaction if session
321
+ end
322
+ end
323
+
324
+ it "clear_sequence_setup should remove custom sequence settings" do
325
+ session = nil
326
+ begin
327
+ session = Session.new
328
+ session.left.begin_db_transaction
329
+ left_sequence_values = session.left.sequence_values 'rr', 'sequence_test'
330
+ right_sequence_values = session.right.sequence_values 'rr', 'sequence_test'
331
+ session.left.update_sequences \
332
+ 'rr', 'sequence_test', 2, 0,
333
+ left_sequence_values, right_sequence_values, 5
334
+ session.left.clear_sequence_setup 'rr', 'sequence_test'
335
+ id1, id2 = get_example_sequence_values(session)
336
+ (id2 - id1).should == 1
337
+ ensure
338
+ session.left.clear_sequence_setup 'rr', 'sequence_test' if session
339
+ session.left.execute "delete from sequence_test" if session
340
+ session.left.rollback_db_transaction if session
341
+ end
342
+ end
343
+
344
+ it "add_big_primary_key should add a 8 byte, auto incrementing primary key" do
345
+ session = nil
346
+ begin
347
+ session = Session.new
348
+ initializer = ReplicationInitializer.new(session)
349
+ initializer.silence_ddl_notices(:left) do
350
+ session.left.drop_table 'big_key_test' if session.left.tables.include? 'big_key_test'
351
+ session.left.create_table 'big_key_test'.to_sym
352
+ session.left.add_column 'big_key_test', :name, :string
353
+ session.left.remove_column 'big_key_test', 'id'
354
+ session.left.add_big_primary_key 'big_key_test', 'id'
355
+ end
356
+ # should auto generate the primary key if not manually specified
357
+ session.left.insert_record 'big_key_test', {'name' => 'bla'}
358
+ session.left.select_one("select id from big_key_test where name = 'bla'")['id'].
359
+ to_i.should > 0
360
+
361
+ # should allow 8 byte values
362
+ session.left.insert_record 'big_key_test', {'id' => 1e18.to_i, 'name' => 'blub'}
363
+ session.left.select_one("select id from big_key_test where name = 'blub'")['id'].
364
+ to_i.should == 1e18.to_i
365
+ end
366
+ end
367
+ end