rubyrep 1.0.5 → 1.0.6
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.
- data/History.txt +6 -0
- data/Manifest.txt +6 -0
- data/lib/rubyrep/configuration.rb +14 -0
- data/lib/rubyrep/logged_change.rb +16 -195
- data/lib/rubyrep/logged_change_loader.rb +196 -0
- data/lib/rubyrep/noisy_connection.rb +80 -0
- data/lib/rubyrep/proxy_connection.rb +2 -9
- data/lib/rubyrep/replication_difference.rb +14 -9
- data/lib/rubyrep/replication_initializer.rb +9 -0
- data/lib/rubyrep/replication_run.rb +29 -6
- data/lib/rubyrep/replication_runner.rb +6 -2
- data/lib/rubyrep/session.rb +85 -43
- data/lib/rubyrep/task_sweeper.rb +77 -0
- data/lib/rubyrep/version.rb +1 -1
- data/lib/rubyrep.rb +3 -0
- data/spec/logged_change_loader_spec.rb +68 -0
- data/spec/logged_change_spec.rb +45 -55
- data/spec/noisy_connection_spec.rb +80 -0
- data/spec/proxy_connection_spec.rb +0 -11
- data/spec/replication_difference_spec.rb +10 -9
- data/spec/replication_helper_spec.rb +17 -14
- data/spec/replication_initializer_spec.rb +22 -0
- data/spec/replication_run_spec.rb +20 -9
- data/spec/session_spec.rb +23 -1
- data/spec/spec_helper.rb +1 -0
- data/spec/task_sweeper_spec.rb +47 -0
- data/spec/two_way_replicator_spec.rb +50 -40
- data.tar.gz.sig +0 -0
- metadata +8 -2
- metadata.gz.sig +0 -0
@@ -8,6 +8,9 @@ module RR
|
|
8
8
|
# The current Session object
|
9
9
|
attr_accessor :session
|
10
10
|
|
11
|
+
# The current TaskSweeper
|
12
|
+
attr_accessor :sweeper
|
13
|
+
|
11
14
|
# Returns the current ReplicationHelper; creates it if necessary
|
12
15
|
def helper
|
13
16
|
@helper ||= ReplicationHelper.new(self)
|
@@ -22,22 +25,29 @@ module RR
|
|
22
25
|
# Executes the replication run.
|
23
26
|
def run
|
24
27
|
return unless [:left, :right].any? do |database|
|
25
|
-
|
26
|
-
|
28
|
+
changes_pending = false
|
29
|
+
t = Thread.new do
|
30
|
+
changes_pending = session.send(database).select_one(
|
27
31
|
"select id from #{session.configuration.options[:rep_prefix]}_pending_changes"
|
28
32
|
) != nil
|
29
33
|
end
|
34
|
+
t.join session.configuration.options[:database_connection_timeout]
|
35
|
+
changes_pending
|
30
36
|
end
|
37
|
+
|
38
|
+
loaders = LoggedChangeLoaders.new(session)
|
39
|
+
|
31
40
|
begin
|
32
41
|
success = false
|
33
42
|
replicator # ensure that replicator is created and has chance to validate settings
|
34
43
|
|
35
44
|
loop do
|
36
45
|
begin
|
37
|
-
|
38
|
-
diff = ReplicationDifference.new
|
46
|
+
loaders.update # ensure the cache of change log records is up-to-date
|
47
|
+
diff = ReplicationDifference.new loaders
|
39
48
|
diff.load
|
40
49
|
break unless diff.loaded?
|
50
|
+
raise "Replication run timed out" if sweeper.terminated?
|
41
51
|
replicator.replicate_difference diff if diff.type != :no_diff
|
42
52
|
rescue Exception => e
|
43
53
|
helper.log_replication_outcome diff, e.message,
|
@@ -50,10 +60,23 @@ module RR
|
|
50
60
|
end
|
51
61
|
end
|
52
62
|
|
63
|
+
# Installs the current sweeper into the database connections
|
64
|
+
def install_sweeper
|
65
|
+
[:left, :right].each do |database|
|
66
|
+
unless session.send(database).respond_to?(:sweeper)
|
67
|
+
session.send(database).send(:extend, NoisyConnection)
|
68
|
+
end
|
69
|
+
session.send(database).sweeper = sweeper
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
53
73
|
# Creates a new ReplicationRun instance.
|
54
74
|
# * +session+: the current Session
|
55
|
-
|
75
|
+
# * +sweeper+: the current TaskSweeper
|
76
|
+
def initialize(session, sweeper)
|
56
77
|
self.session = session
|
78
|
+
self.sweeper = sweeper
|
79
|
+
install_sweeper
|
57
80
|
end
|
58
81
|
end
|
59
|
-
end
|
82
|
+
end
|
@@ -108,8 +108,12 @@ EOS
|
|
108
108
|
until termination_requested do
|
109
109
|
begin
|
110
110
|
session.refresh
|
111
|
-
|
112
|
-
|
111
|
+
timeout = session.configuration.options[:database_connection_timeout]
|
112
|
+
terminated = TaskSweeper.timeout(timeout) do |sweeper|
|
113
|
+
run = ReplicationRun.new session, sweeper
|
114
|
+
run.run
|
115
|
+
end.terminated?
|
116
|
+
raise "replication run timed out" if terminated
|
113
117
|
rescue Exception => e
|
114
118
|
now = Time.now.iso8601
|
115
119
|
$stderr.puts "#{now} Exception caught: #{e}"
|
data/lib/rubyrep/session.rb
CHANGED
@@ -83,32 +83,7 @@ module RR
|
|
83
83
|
@table_map[db_arm][table] || table
|
84
84
|
end
|
85
85
|
|
86
|
-
#
|
87
|
-
# db_arm:: should be either :left or :right
|
88
|
-
# config:: the rubyrep Configuration
|
89
|
-
def db_connect(db_arm, config)
|
90
|
-
arm_config = config.send db_arm
|
91
|
-
@proxies[db_arm] = DatabaseProxy.new
|
92
|
-
@connections[db_arm] = @proxies[db_arm].create_session arm_config
|
93
|
-
end
|
94
|
-
|
95
|
-
# Does the actual work of establishing a proxy connection
|
96
|
-
# db_arm:: should be either :left or :right
|
97
|
-
# config:: the rubyrep Configuration
|
98
|
-
def proxy_connect(db_arm, config)
|
99
|
-
arm_config = config.send db_arm
|
100
|
-
if arm_config.include? :proxy_host
|
101
|
-
drb_url = "druby://#{arm_config[:proxy_host]}:#{arm_config[:proxy_port]}"
|
102
|
-
@proxies[db_arm] = DRbObject.new nil, drb_url
|
103
|
-
else
|
104
|
-
# If one connection goes through a proxy, so has the other one.
|
105
|
-
# So if necessary, create a "fake" proxy
|
106
|
-
@proxies[db_arm] = DatabaseProxy.new
|
107
|
-
end
|
108
|
-
@connections[db_arm] = @proxies[db_arm].create_session arm_config
|
109
|
-
end
|
110
|
-
|
111
|
-
# True if proxy connections are used
|
86
|
+
# Returns +true+ if proxy connections are used
|
112
87
|
def proxied?
|
113
88
|
[configuration.left, configuration.right].any? \
|
114
89
|
{|arm_config| arm_config.include? :proxy_host}
|
@@ -144,9 +119,90 @@ module RR
|
|
144
119
|
end
|
145
120
|
end
|
146
121
|
|
147
|
-
#
|
122
|
+
# Returns +true+ if the specified database connection is not alive.
|
123
|
+
# * +database+: target database (either +:left+ or :+right+)
|
124
|
+
def database_unreachable?(database)
|
125
|
+
unreachable = true
|
126
|
+
Thread.new do
|
127
|
+
begin
|
128
|
+
if send(database) && send(database).select_one("select 1+1 as x")['x'].to_i == 2
|
129
|
+
unreachable = false # database is actually reachable
|
130
|
+
end
|
131
|
+
end rescue nil
|
132
|
+
end.join configuration.options[:database_connection_timeout]
|
133
|
+
unreachable
|
134
|
+
end
|
135
|
+
|
136
|
+
# Disconnnects the specified database
|
137
|
+
# * +database+: the target database (either :+left+ or :+right+)
|
138
|
+
def disconnect_database(database)
|
139
|
+
proxy, connection = @proxies[database], @connection[database]
|
140
|
+
@proxies[database] = nil
|
141
|
+
@connections[database] = nil
|
142
|
+
if proxy
|
143
|
+
proxy.destroy_session(connection)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Refreshes both database connections
|
148
148
|
def refresh
|
149
|
-
[:left, :right].each {|database|
|
149
|
+
[:left, :right].each {|database| refresh_database_connection database}
|
150
|
+
end
|
151
|
+
|
152
|
+
# Refreshes the specified database connection.
|
153
|
+
# (I. e. reestablish if not active anymore.)
|
154
|
+
# * +database+: target database (either :+left+ or :+right+)
|
155
|
+
def refresh_database_connection(database)
|
156
|
+
if database_unreachable?(database)
|
157
|
+
# step 1: disconnect both database connection (if still possible)
|
158
|
+
begin
|
159
|
+
Thread.new do
|
160
|
+
disconnect_database database rescue nil
|
161
|
+
end.join configuration.options[:database_connection_timeout]
|
162
|
+
end
|
163
|
+
|
164
|
+
connect_exception = nil
|
165
|
+
# step 2: try to reconnect the database
|
166
|
+
Thread.new do
|
167
|
+
begin
|
168
|
+
connect_database database
|
169
|
+
rescue Exception => e
|
170
|
+
# save exception so it can be rethrown outside of the thread
|
171
|
+
connect_exception = e
|
172
|
+
end
|
173
|
+
end.join configuration.options[:database_connection_timeout]
|
174
|
+
raise connect_exception if connect_exception
|
175
|
+
|
176
|
+
# step 3: verify if database connections actually work (to detect silent connection failures)
|
177
|
+
if database_unreachable?(database)
|
178
|
+
raise "no connection to '#{database}' database"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# Set up the (proxied or direct) database connections to the specified
|
184
|
+
# database.
|
185
|
+
# * +database+: the target database (either :+left+ or :+right+)
|
186
|
+
def connect_database(database)
|
187
|
+
if configuration.left == configuration.right and database == :right
|
188
|
+
# If both database configurations point to the same database
|
189
|
+
# then don't create the database connection twice.
|
190
|
+
# Assumes that the left database is always connected before the right one.
|
191
|
+
self.right = self.left
|
192
|
+
else
|
193
|
+
# Connect the database / proxy
|
194
|
+
arm_config = configuration.send database
|
195
|
+
if arm_config.include? :proxy_host
|
196
|
+
drb_url = "druby://#{arm_config[:proxy_host]}:#{arm_config[:proxy_port]}"
|
197
|
+
@proxies[database] = DRbObject.new nil, drb_url
|
198
|
+
else
|
199
|
+
# Create fake proxy
|
200
|
+
@proxies[database] = DatabaseProxy.new
|
201
|
+
end
|
202
|
+
@connections[database] = @proxies[database].create_session arm_config
|
203
|
+
|
204
|
+
send(database).manual_primary_keys = manual_primary_keys(database)
|
205
|
+
end
|
150
206
|
end
|
151
207
|
|
152
208
|
# Creates a new rubyrep session with the provided Configuration
|
@@ -157,21 +213,7 @@ module RR
|
|
157
213
|
# Keep the database configuration for future reference
|
158
214
|
self.configuration = config
|
159
215
|
|
160
|
-
|
161
|
-
connection_method = proxied? ? :proxy_connect : :db_connect
|
162
|
-
|
163
|
-
# Connect the left database / proxy
|
164
|
-
self.send connection_method, :left, configuration
|
165
|
-
left.manual_primary_keys = manual_primary_keys(:left)
|
166
|
-
|
167
|
-
# If both database configurations point to the same database
|
168
|
-
# then don't create the database connection twice
|
169
|
-
if configuration.left == configuration.right
|
170
|
-
self.right = self.left
|
171
|
-
else
|
172
|
-
self.send connection_method, :right, configuration
|
173
|
-
right.manual_primary_keys = manual_primary_keys(:right)
|
174
|
-
end
|
216
|
+
refresh
|
175
217
|
end
|
176
218
|
end
|
177
219
|
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module RR
|
2
|
+
|
3
|
+
# Monitors and cancels stalled tasks
|
4
|
+
class TaskSweeper
|
5
|
+
|
6
|
+
# Executes the give block in a separate Thread.
|
7
|
+
# Returns if block is finished or stalled.
|
8
|
+
# The block must call regular #ping to announce it is not stalled.
|
9
|
+
# * +timeout_period+:
|
10
|
+
# Maximum time (in seonds) without ping, after which a task is considered stalled.
|
11
|
+
# Returns the created sweeper (allows checking if task was terminated).
|
12
|
+
def self.timeout(timeout_period)
|
13
|
+
sweeper = TaskSweeper.new(timeout_period)
|
14
|
+
sweeper.send(:timeout) {yield sweeper}
|
15
|
+
sweeper
|
16
|
+
end
|
17
|
+
|
18
|
+
# Time in seconds after which a task is considered stalled. Timer is reset
|
19
|
+
# by calling #ping.
|
20
|
+
attr_accessor :timeout_period
|
21
|
+
|
22
|
+
# Must be called by the executed task to announce it is still alive
|
23
|
+
def ping
|
24
|
+
self.last_ping = Time.now
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns +true+ if the task was timed out.
|
28
|
+
# The terminated task is expected to free all resources and exit.
|
29
|
+
def terminated?
|
30
|
+
terminated
|
31
|
+
end
|
32
|
+
|
33
|
+
# Waits without timeout till the task executing thread is finished
|
34
|
+
def join
|
35
|
+
thread && thread.join
|
36
|
+
end
|
37
|
+
|
38
|
+
# Creates a new TaskSweeper
|
39
|
+
# * +timeout_period+: timeout value in seconds
|
40
|
+
def initialize(timeout_period)
|
41
|
+
self.timeout_period = timeout_period
|
42
|
+
self.terminated = false
|
43
|
+
self.last_ping = Time.now
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
# Time of last ping
|
49
|
+
attr_accessor :last_ping
|
50
|
+
|
51
|
+
# Set to +true+ if the executed task has timed out
|
52
|
+
attr_accessor :terminated
|
53
|
+
|
54
|
+
# The task executing thread
|
55
|
+
attr_accessor :thread
|
56
|
+
|
57
|
+
# Executes the given block and times it out if stalled.
|
58
|
+
def timeout
|
59
|
+
exception = nil
|
60
|
+
self.thread = Thread.new do
|
61
|
+
begin
|
62
|
+
yield
|
63
|
+
rescue Exception => e
|
64
|
+
# save exception so it can be rethrown outside of the thread
|
65
|
+
exception = e
|
66
|
+
end
|
67
|
+
end
|
68
|
+
while self.thread.join(self.timeout_period) == nil do
|
69
|
+
if self.last_ping < Time.now - self.timeout_period
|
70
|
+
self.terminated = true
|
71
|
+
break
|
72
|
+
end
|
73
|
+
end
|
74
|
+
raise exception if exception
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/lib/rubyrep/version.rb
CHANGED
data/lib/rubyrep.rb
CHANGED
@@ -42,15 +42,18 @@ require 'syncers/syncers'
|
|
42
42
|
require 'syncers/two_way_syncer'
|
43
43
|
require 'sync_runner'
|
44
44
|
require 'trigger_mode_switcher'
|
45
|
+
require 'logged_change_loader'
|
45
46
|
require 'logged_change'
|
46
47
|
require 'replication_difference'
|
47
48
|
require 'replication_helper'
|
48
49
|
require 'replicators/replicators'
|
49
50
|
require 'replicators/two_way_replicator'
|
51
|
+
require 'task_sweeper'
|
50
52
|
require 'replication_run'
|
51
53
|
require 'replication_runner'
|
52
54
|
require 'uninstall_runner'
|
53
55
|
require 'generate_runner'
|
56
|
+
require 'noisy_connection'
|
54
57
|
|
55
58
|
Dir["#{File.dirname(__FILE__)}/rubyrep/connection_extenders/*.rb"].each do |extender|
|
56
59
|
# jdbc_extender.rb is only loaded if we are running on jruby
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper.rb'
|
2
|
+
|
3
|
+
include RR
|
4
|
+
|
5
|
+
describe LoggedChangeLoaders do
|
6
|
+
before(:each) do
|
7
|
+
Initializer.configuration = standard_config
|
8
|
+
end
|
9
|
+
|
10
|
+
it "initializers should create both logged change loaders" do
|
11
|
+
session = Session.new
|
12
|
+
loaders = LoggedChangeLoaders.new(session)
|
13
|
+
loaders[:left].session.should == session
|
14
|
+
loaders[:left].database.should == :left
|
15
|
+
loaders[:right].database.should == :right
|
16
|
+
end
|
17
|
+
|
18
|
+
it "update should execute a forced update of both logged change loaders" do
|
19
|
+
session = Session.new
|
20
|
+
loaders = LoggedChangeLoaders.new(session)
|
21
|
+
loaders[:left].should_receive(:update).with(:forced => true)
|
22
|
+
loaders[:right].should_receive(:update).with(:forced => true)
|
23
|
+
loaders.update
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
describe LoggedChangeLoader do
|
29
|
+
before(:each) do
|
30
|
+
Initializer.configuration = standard_config
|
31
|
+
end
|
32
|
+
|
33
|
+
# Note:
|
34
|
+
# LoggedChangeLoader is a helper for LoggedChange.
|
35
|
+
# It is tested through the specs for LoggedChange.
|
36
|
+
|
37
|
+
it "oldest_change_time should return nil if there are no changes" do
|
38
|
+
session = Session.new
|
39
|
+
session.left.execute "delete from rr_pending_changes"
|
40
|
+
loader = LoggedChangeLoader.new session, :left
|
41
|
+
loader.oldest_change_time.should be_nil
|
42
|
+
end
|
43
|
+
|
44
|
+
it "oldest_change_time should return the time of the oldest change" do
|
45
|
+
session = Session.new
|
46
|
+
session.left.begin_db_transaction
|
47
|
+
begin
|
48
|
+
time = Time.now
|
49
|
+
session.left.insert_record 'rr_pending_changes', {
|
50
|
+
'change_table' => 'left_table',
|
51
|
+
'change_key' => 'id|1',
|
52
|
+
'change_type' => 'I',
|
53
|
+
'change_time' => time
|
54
|
+
}
|
55
|
+
session.left.insert_record 'rr_pending_changes', {
|
56
|
+
'change_table' => 'left_table',
|
57
|
+
'change_key' => 'id|2',
|
58
|
+
'change_type' => 'I',
|
59
|
+
'change_time' => 100.seconds.from_now
|
60
|
+
}
|
61
|
+
loader = LoggedChangeLoader.new session, :left
|
62
|
+
loader.oldest_change_time.should.to_s == time.to_s
|
63
|
+
ensure
|
64
|
+
session.left.rollback_db_transaction
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
data/spec/logged_change_spec.rb
CHANGED
@@ -9,7 +9,8 @@ describe LoggedChange do
|
|
9
9
|
|
10
10
|
it "initialize should store session and database" do
|
11
11
|
session = Session.new
|
12
|
-
|
12
|
+
loader = LoggedChangeLoader.new session, :left
|
13
|
+
change = LoggedChange.new loader
|
13
14
|
change.session.should == session
|
14
15
|
change.database.should == :left
|
15
16
|
end
|
@@ -37,7 +38,8 @@ describe LoggedChange do
|
|
37
38
|
'change_type' => 'I',
|
38
39
|
'change_time' => Time.now
|
39
40
|
}
|
40
|
-
|
41
|
+
loader = LoggedChangeLoader.new session, :left
|
42
|
+
change = LoggedChange.new loader
|
41
43
|
change.load_specified 'left_table', {'id' => '2'}
|
42
44
|
|
43
45
|
change.table.should == 'left_table'
|
@@ -62,7 +64,8 @@ describe LoggedChange do
|
|
62
64
|
'change_type' => 'I',
|
63
65
|
'change_time' => Time.now
|
64
66
|
}
|
65
|
-
|
67
|
+
loader = LoggedChangeLoader.new session, :left
|
68
|
+
change = LoggedChange.new loader
|
66
69
|
change.load_specified 'scanner_records', {'id1' => 1, 'id2' => 2}
|
67
70
|
|
68
71
|
change.table.should == 'scanner_records'
|
@@ -83,7 +86,8 @@ describe LoggedChange do
|
|
83
86
|
'change_type' => 'I',
|
84
87
|
'change_time' => Time.now
|
85
88
|
}
|
86
|
-
|
89
|
+
loader = LoggedChangeLoader.new session, :left
|
90
|
+
change = LoggedChange.new loader
|
87
91
|
change.load_specified 'left_table', {'id' => 1}
|
88
92
|
|
89
93
|
session.left.
|
@@ -113,7 +117,8 @@ describe LoggedChange do
|
|
113
117
|
'change_type' => 'U',
|
114
118
|
'change_time' => t2
|
115
119
|
}
|
116
|
-
|
120
|
+
loader = LoggedChangeLoader.new session, :left
|
121
|
+
change = LoggedChange.new loader
|
117
122
|
change.load_specified 'left_table', {'id' => 1}
|
118
123
|
|
119
124
|
change.first_changed_at.to_s.should == t1.to_s
|
@@ -141,7 +146,8 @@ describe LoggedChange do
|
|
141
146
|
'change_type' => 'U',
|
142
147
|
'change_time' => Time.now
|
143
148
|
}
|
144
|
-
|
149
|
+
loader = LoggedChangeLoader.new session, :left
|
150
|
+
change = LoggedChange.new loader
|
145
151
|
change.load_specified 'left_table', {'id' => 1}
|
146
152
|
|
147
153
|
change.type.should == :update
|
@@ -175,7 +181,8 @@ describe LoggedChange do
|
|
175
181
|
'change_type' => 'D',
|
176
182
|
'change_time' => Time.now
|
177
183
|
}
|
178
|
-
|
184
|
+
loader = LoggedChangeLoader.new session, :left
|
185
|
+
change = LoggedChange.new loader
|
179
186
|
change.load_specified 'left_table', {'id' => '1'}
|
180
187
|
|
181
188
|
change.type.should == :no_change
|
@@ -215,7 +222,8 @@ describe LoggedChange do
|
|
215
222
|
'change_type' => 'U',
|
216
223
|
'change_time' => Time.now
|
217
224
|
}
|
218
|
-
|
225
|
+
loader = LoggedChangeLoader.new session, :left
|
226
|
+
change = LoggedChange.new loader
|
219
227
|
change.load_specified 'left_table', {'id' => '1'}
|
220
228
|
change.type.should == :insert
|
221
229
|
change.key.should == {'id' => '2'}
|
@@ -233,8 +241,8 @@ describe LoggedChange do
|
|
233
241
|
'change_type' => 'I',
|
234
242
|
'change_time' => Time.now
|
235
243
|
}
|
236
|
-
|
237
|
-
change = LoggedChange.new
|
244
|
+
loader.update :forced => true
|
245
|
+
change = LoggedChange.new loader
|
238
246
|
change.load_specified 'left_table', {'id' => '5'}
|
239
247
|
change.type.should == :update
|
240
248
|
change.key.should == {'id' => '5'}
|
@@ -254,7 +262,8 @@ describe LoggedChange do
|
|
254
262
|
'change_type' => 'I',
|
255
263
|
'change_time' => Time.now
|
256
264
|
}
|
257
|
-
|
265
|
+
loader = LoggedChangeLoader.new session, :left
|
266
|
+
change = LoggedChange.new loader
|
258
267
|
change.load_specified 'scanner_records', {'id' => '1'}
|
259
268
|
|
260
269
|
change.table.should == 'scanner_records'
|
@@ -275,7 +284,8 @@ describe LoggedChange do
|
|
275
284
|
session = Session.new
|
276
285
|
session.left.begin_db_transaction
|
277
286
|
begin
|
278
|
-
|
287
|
+
loader = LoggedChangeLoader.new session, :left
|
288
|
+
change = LoggedChange.new loader
|
279
289
|
change.load_specified 'scanner_records', {'id' => '1'}
|
280
290
|
|
281
291
|
change.table.should == 'scanner_records'
|
@@ -307,7 +317,8 @@ describe LoggedChange do
|
|
307
317
|
'change_type' => 'U',
|
308
318
|
'change_time' => Time.now
|
309
319
|
}
|
310
|
-
|
320
|
+
loader = LoggedChangeLoader.new session, :left
|
321
|
+
change = LoggedChange.new loader
|
311
322
|
change.load_specified 'left_table', {'id' => '1'}
|
312
323
|
session.left.insert_record 'rr_pending_changes', {
|
313
324
|
'change_table' => 'left_table',
|
@@ -315,7 +326,7 @@ describe LoggedChange do
|
|
315
326
|
'change_type' => 'D',
|
316
327
|
'change_time' => Time.now
|
317
328
|
}
|
318
|
-
|
329
|
+
loader.update :forced => true
|
319
330
|
change.load
|
320
331
|
|
321
332
|
change.table.should == 'left_table'
|
@@ -341,7 +352,8 @@ describe LoggedChange do
|
|
341
352
|
'change_type' => 'U',
|
342
353
|
'change_time' => Time.now
|
343
354
|
}
|
344
|
-
|
355
|
+
loader = LoggedChangeLoader.new session, :left
|
356
|
+
change = LoggedChange.new loader
|
345
357
|
change.load_specified 'left_table', {'id' => '1'}
|
346
358
|
session.left.insert_record 'rr_pending_changes', {
|
347
359
|
'change_table' => 'left_table',
|
@@ -350,7 +362,7 @@ describe LoggedChange do
|
|
350
362
|
'change_type' => 'U',
|
351
363
|
'change_time' => Time.now
|
352
364
|
}
|
353
|
-
|
365
|
+
loader.update :forced => true
|
354
366
|
change.load
|
355
367
|
|
356
368
|
change.table.should == 'left_table'
|
@@ -362,39 +374,9 @@ describe LoggedChange do
|
|
362
374
|
end
|
363
375
|
end
|
364
376
|
|
365
|
-
it "oldest_change_time should return nil if there are no changes" do
|
366
|
-
session = Session.new
|
367
|
-
session.left.execute "delete from rr_pending_changes"
|
368
|
-
change = LoggedChange.new session, :left
|
369
|
-
change.oldest_change_time.should be_nil
|
370
|
-
end
|
371
|
-
|
372
|
-
it "oldest_change_time should return the time of the oldest change" do
|
373
|
-
session = Session.new
|
374
|
-
session.left.begin_db_transaction
|
375
|
-
begin
|
376
|
-
time = Time.now
|
377
|
-
session.left.insert_record 'rr_pending_changes', {
|
378
|
-
'change_table' => 'left_table',
|
379
|
-
'change_key' => 'id|1',
|
380
|
-
'change_type' => 'I',
|
381
|
-
'change_time' => time
|
382
|
-
}
|
383
|
-
session.left.insert_record 'rr_pending_changes', {
|
384
|
-
'change_table' => 'left_table',
|
385
|
-
'change_key' => 'id|2',
|
386
|
-
'change_type' => 'I',
|
387
|
-
'change_time' => 100.seconds.from_now
|
388
|
-
}
|
389
|
-
change = LoggedChange.new session, :left
|
390
|
-
change.oldest_change_time.should.to_s == time.to_s
|
391
|
-
ensure
|
392
|
-
session.left.rollback_db_transaction
|
393
|
-
end
|
394
|
-
end
|
395
|
-
|
396
377
|
it "key_from_raw_key should return the correct column_name => value hash for the given key" do
|
397
|
-
|
378
|
+
loader = LoggedChangeLoader.new Session.new, :left
|
379
|
+
change = LoggedChange.new loader
|
398
380
|
change.key_to_hash("a|1|b|2").should == {
|
399
381
|
'a' => '1',
|
400
382
|
'b' => '2'
|
@@ -402,7 +384,8 @@ describe LoggedChange do
|
|
402
384
|
end
|
403
385
|
|
404
386
|
it "key_from_raw_key should work with multi character key_sep strings" do
|
405
|
-
|
387
|
+
loader = LoggedChangeLoader.new Session.new, :left
|
388
|
+
change = LoggedChange.new loader
|
406
389
|
change.stub!(:key_sep).and_return('BLA')
|
407
390
|
change.key_to_hash("aBLA1BLAbBLA2").should == {
|
408
391
|
'a' => '1',
|
@@ -411,7 +394,8 @@ describe LoggedChange do
|
|
411
394
|
end
|
412
395
|
|
413
396
|
it "load_oldest should not load a change if none available" do
|
414
|
-
|
397
|
+
loader = LoggedChangeLoader.new Session.new, :left
|
398
|
+
change = LoggedChange.new loader
|
415
399
|
change.should_not_receive :load_specified
|
416
400
|
change.load_oldest
|
417
401
|
end
|
@@ -432,7 +416,8 @@ describe LoggedChange do
|
|
432
416
|
'change_type' => 'I',
|
433
417
|
'change_time' => Time.now
|
434
418
|
}
|
435
|
-
|
419
|
+
loader = LoggedChangeLoader.new session, :left
|
420
|
+
change = LoggedChange.new loader
|
436
421
|
change.load_oldest
|
437
422
|
|
438
423
|
change.key.should == {'id' => '1'}
|
@@ -463,7 +448,8 @@ describe LoggedChange do
|
|
463
448
|
'change_type' => 'I',
|
464
449
|
'change_time' => Time.now
|
465
450
|
}
|
466
|
-
|
451
|
+
loader = LoggedChangeLoader.new session, :left
|
452
|
+
change = LoggedChange.new loader
|
467
453
|
change.load_oldest
|
468
454
|
|
469
455
|
change.type.should == :insert
|
@@ -473,8 +459,12 @@ describe LoggedChange do
|
|
473
459
|
end
|
474
460
|
end
|
475
461
|
|
476
|
-
it "to_yaml should blank out session" do
|
477
|
-
|
478
|
-
|
462
|
+
it "to_yaml should blank out session and loader" do
|
463
|
+
session = Session.new
|
464
|
+
loader = LoggedChangeLoader.new session, :left
|
465
|
+
change = LoggedChange.new loader
|
466
|
+
yaml = change.to_yaml
|
467
|
+
yaml.should_not =~ /session/
|
468
|
+
yaml.should_not =~ /loader/
|
479
469
|
end
|
480
470
|
end
|