rubyrep 1.0.5 → 1.0.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/History.txt
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
== 1.0.6 2009-07-25
|
2
|
+
|
3
|
+
* Bug fix: do not assume anymore that replication log events appear in sequential order
|
4
|
+
* Feature: increased resilience of rubyrep to network timeouts
|
5
|
+
* Feature: introduced :after_infrastructure_setup handler
|
6
|
+
|
1
7
|
== 1.0.5 2009-07-03
|
2
8
|
|
3
9
|
* Bug fix: rubyrep replication runs should survive update or delete attempts rejected by the database.
|
data/Manifest.txt
CHANGED
@@ -28,6 +28,8 @@ lib/rubyrep/generate_runner.rb
|
|
28
28
|
lib/rubyrep/initializer.rb
|
29
29
|
lib/rubyrep/log_helper.rb
|
30
30
|
lib/rubyrep/logged_change.rb
|
31
|
+
lib/rubyrep/logged_change_loader.rb
|
32
|
+
lib/rubyrep/noisy_connection.rb
|
31
33
|
lib/rubyrep/proxied_table_scan.rb
|
32
34
|
lib/rubyrep/proxy_block_cursor.rb
|
33
35
|
lib/rubyrep/proxy_connection.rb
|
@@ -60,6 +62,7 @@ lib/rubyrep/table_scan_helper.rb
|
|
60
62
|
lib/rubyrep/table_sorter.rb
|
61
63
|
lib/rubyrep/table_spec_resolver.rb
|
62
64
|
lib/rubyrep/table_sync.rb
|
65
|
+
lib/rubyrep/task_sweeper.rb
|
63
66
|
lib/rubyrep/trigger_mode_switcher.rb
|
64
67
|
lib/rubyrep/type_casting_cursor.rb
|
65
68
|
lib/rubyrep/uninstall_runner.rb
|
@@ -89,7 +92,9 @@ spec/dolphins.jpg
|
|
89
92
|
spec/generate_runner_spec.rb
|
90
93
|
spec/initializer_spec.rb
|
91
94
|
spec/log_helper_spec.rb
|
95
|
+
spec/logged_change_loader_spec.rb
|
92
96
|
spec/logged_change_spec.rb
|
97
|
+
spec/noisy_connection_spec.rb
|
93
98
|
spec/postgresql_replication_spec.rb
|
94
99
|
spec/postgresql_schema_support_spec.rb
|
95
100
|
spec/postgresql_support_spec.rb
|
@@ -126,6 +131,7 @@ spec/table_scan_spec.rb
|
|
126
131
|
spec/table_sorter_spec.rb
|
127
132
|
spec/table_spec_resolver_spec.rb
|
128
133
|
spec/table_sync_spec.rb
|
134
|
+
spec/task_sweeper_spec.rb
|
129
135
|
spec/trigger_mode_switcher_spec.rb
|
130
136
|
spec/two_way_replicator_spec.rb
|
131
137
|
spec/two_way_syncer_spec.rb
|
@@ -112,6 +112,20 @@ module RR
|
|
112
112
|
# * :+replication_interval+: time in seconds between replication runs
|
113
113
|
# * :+database_connection_timeout+:
|
114
114
|
# Time in seconds after which database connections time out.
|
115
|
+
# * :+:after_infrastructure_setup+:
|
116
|
+
# A Proc that is called after the replication infrastructure tables are
|
117
|
+
# set up. Useful to e. g. tweak the access settings for the table.
|
118
|
+
# The block is called with the current Session object.
|
119
|
+
# The block is called every time replication is started, even if the
|
120
|
+
# the infrastructure tables already existed.
|
121
|
+
#
|
122
|
+
# Example of an :+after_infrastructure_setup+ handler:
|
123
|
+
# lambda do |session|
|
124
|
+
# [:left, :right].each do |database|
|
125
|
+
# session.send(left).execute \
|
126
|
+
# "GRANT SELECT, UPDATE, INSERT ON rr_pending_changes TO scott"
|
127
|
+
# end
|
128
|
+
# end
|
115
129
|
attr_reader :options
|
116
130
|
|
117
131
|
# Merges the specified +options+ hash into the existing options
|
@@ -1,184 +1,5 @@
|
|
1
1
|
module RR
|
2
2
|
|
3
|
-
class Session
|
4
|
-
|
5
|
-
# Returns the +LoggedChangeLoader+ of the specified database.
|
6
|
-
# * database: either :+left+ or :+right+
|
7
|
-
def change_loader(database)
|
8
|
-
@change_loaders ||= {}
|
9
|
-
unless change_loader = @change_loaders[database]
|
10
|
-
change_loader = @change_loaders[database] = LoggedChangeLoader.new(self, database)
|
11
|
-
end
|
12
|
-
change_loader
|
13
|
-
end
|
14
|
-
|
15
|
-
# Forces an update of the change log cache
|
16
|
-
def reload_changes
|
17
|
-
change_loader(:left).update :forced => true
|
18
|
-
change_loader(:right).update :forced => true
|
19
|
-
end
|
20
|
-
|
21
|
-
end
|
22
|
-
|
23
|
-
# Caches the entries in the change log table
|
24
|
-
class LoggedChangeLoader
|
25
|
-
|
26
|
-
# The current +Session+.
|
27
|
-
attr_accessor :session
|
28
|
-
|
29
|
-
# The current +ProxyConnection+.
|
30
|
-
attr_accessor :connection
|
31
|
-
|
32
|
-
# Index to the next unprocessed change in the +change_array+.
|
33
|
-
attr_accessor :current_index
|
34
|
-
|
35
|
-
# ID of the last cached change log record.
|
36
|
-
attr_accessor :current_id
|
37
|
-
|
38
|
-
# Array with all cached changes.
|
39
|
-
# Processed change log records are replaced with +nil+.
|
40
|
-
attr_accessor :change_array
|
41
|
-
|
42
|
-
# Tree (hash) structure for fast access to all cached changes.
|
43
|
-
# First level of tree:
|
44
|
-
# * key: table name
|
45
|
-
# * value: 2nd level tree
|
46
|
-
# 2nd level tree:
|
47
|
-
# * key: the change_key value of the according change log records.
|
48
|
-
# * value:
|
49
|
-
# An array of according change log records (column_name => value hash).
|
50
|
-
# Additional entry of each change log hash:
|
51
|
-
# * key: 'array_index'
|
52
|
-
# * value: index to the change log record in +change_array+
|
53
|
-
attr_accessor :change_tree
|
54
|
-
|
55
|
-
# Date of last update of the cache
|
56
|
-
attr_accessor :last_updated
|
57
|
-
|
58
|
-
# Initializes / resets the cache.
|
59
|
-
def init_cache
|
60
|
-
self.change_tree = {}
|
61
|
-
self.change_array = []
|
62
|
-
self.current_index = 0
|
63
|
-
end
|
64
|
-
private :init_cache
|
65
|
-
|
66
|
-
# Create a new change log record cache.
|
67
|
-
# * +session+: The current +Session+
|
68
|
-
# * +database+: Either :+left+ or :+right+
|
69
|
-
def initialize(session, database)
|
70
|
-
self.session = session
|
71
|
-
self.connection = session.send(database)
|
72
|
-
|
73
|
-
init_cache
|
74
|
-
self.current_id = -1
|
75
|
-
self.last_updated = 1.year.ago
|
76
|
-
end
|
77
|
-
|
78
|
-
# Updates the cache.
|
79
|
-
# Options is a hash determining when the update is actually executed:
|
80
|
-
# * :+expire_time+: cache is older than the given number of seconds
|
81
|
-
# * :+forced+: if +true+ update the cache even if not yet expired
|
82
|
-
def update(options = {:forced => false, :expire_time => 1})
|
83
|
-
return unless options[:forced] or Time.now - self.last_updated >= options[:expire_time]
|
84
|
-
|
85
|
-
self.last_updated = Time.now
|
86
|
-
|
87
|
-
# First, let's use a LIMIT clause (via :row_buffer_size option) to verify
|
88
|
-
# if there are any pending changes.
|
89
|
-
# (If there are many pending changes, this is (at least with PostgreSQL)
|
90
|
-
# much faster.)
|
91
|
-
cursor = connection.select_cursor(
|
92
|
-
:table => change_log_table,
|
93
|
-
:from => {'id' => current_id},
|
94
|
-
:exclude_starting_row => true,
|
95
|
-
:row_buffer_size => 1
|
96
|
-
)
|
97
|
-
return unless cursor.next?
|
98
|
-
|
99
|
-
# Something is here. Let's actually load it.
|
100
|
-
cursor = connection.select_cursor(
|
101
|
-
:table => change_log_table,
|
102
|
-
:from => {'id' => current_id},
|
103
|
-
:exclude_starting_row => true,
|
104
|
-
:type_cast => true
|
105
|
-
)
|
106
|
-
while cursor.next?
|
107
|
-
change = cursor.next_row
|
108
|
-
self.current_id = change['id']
|
109
|
-
self.change_array << change
|
110
|
-
change['array_index'] = self.change_array.size - 1
|
111
|
-
|
112
|
-
table_change_tree = change_tree[change['change_table']] ||= {}
|
113
|
-
key_changes = table_change_tree[change['change_key']] ||= []
|
114
|
-
key_changes << change
|
115
|
-
end
|
116
|
-
cursor.clear
|
117
|
-
end
|
118
|
-
|
119
|
-
# Returns the creation time of the oldest unprocessed change log record.
|
120
|
-
def oldest_change_time
|
121
|
-
change = oldest_change
|
122
|
-
change['change_time'] if change
|
123
|
-
end
|
124
|
-
|
125
|
-
# Returns the oldest unprocessed change log record (column_name => value hash).
|
126
|
-
def oldest_change
|
127
|
-
update
|
128
|
-
oldest_change = nil
|
129
|
-
unless change_array.empty?
|
130
|
-
while (oldest_change = change_array[self.current_index]) == nil
|
131
|
-
self.current_index += 1
|
132
|
-
end
|
133
|
-
end
|
134
|
-
oldest_change
|
135
|
-
end
|
136
|
-
|
137
|
-
# Returns the specified change log record (column_name => value hash).
|
138
|
-
# * +change_table+: the name of the table that was changed
|
139
|
-
# * +change_key+: the change key of the modified record
|
140
|
-
def load(change_table, change_key)
|
141
|
-
update
|
142
|
-
change = nil
|
143
|
-
table_change_tree = change_tree[change_table]
|
144
|
-
if table_change_tree
|
145
|
-
key_changes = table_change_tree[change_key]
|
146
|
-
if key_changes
|
147
|
-
# get change object and delete from key_changes
|
148
|
-
change = key_changes.shift
|
149
|
-
|
150
|
-
# delete change from change_array
|
151
|
-
change_array[change['array_index']] = nil
|
152
|
-
|
153
|
-
# delete change from database
|
154
|
-
connection.execute "delete from #{change_log_table} where id = #{change['id']}"
|
155
|
-
|
156
|
-
# delete key_changes if empty
|
157
|
-
if key_changes.empty?
|
158
|
-
table_change_tree.delete change_key
|
159
|
-
end
|
160
|
-
|
161
|
-
# delete table_change_tree if empty
|
162
|
-
if table_change_tree.empty?
|
163
|
-
change_tree.delete change_table
|
164
|
-
end
|
165
|
-
|
166
|
-
# reset everything if no more changes remain
|
167
|
-
if change_tree.empty?
|
168
|
-
init_cache
|
169
|
-
end
|
170
|
-
end
|
171
|
-
end
|
172
|
-
change
|
173
|
-
end
|
174
|
-
|
175
|
-
# Returns the name of the change log table
|
176
|
-
def change_log_table
|
177
|
-
@change_log_table ||= "#{session.configuration.options[:rep_prefix]}_pending_changes"
|
178
|
-
end
|
179
|
-
private :change_log_table
|
180
|
-
end
|
181
|
-
|
182
3
|
# Describes a single logged record change.
|
183
4
|
#
|
184
5
|
# Note:
|
@@ -187,11 +8,18 @@ module RR
|
|
187
8
|
# Also at the end of change processing the transaction must be committed.
|
188
9
|
class LoggedChange
|
189
10
|
|
11
|
+
# The current LoggedChangeLoader
|
12
|
+
attr_accessor :loader
|
13
|
+
|
190
14
|
# The current Session
|
191
|
-
|
15
|
+
def session
|
16
|
+
@session ||= loader.session
|
17
|
+
end
|
192
18
|
|
193
|
-
# The database
|
194
|
-
|
19
|
+
# The current database (either +:left+ or +:right+)
|
20
|
+
def database
|
21
|
+
@database ||= loader.database
|
22
|
+
end
|
195
23
|
|
196
24
|
# The name of the changed table
|
197
25
|
attr_accessor :table
|
@@ -213,11 +41,10 @@ module RR
|
|
213
41
|
attr_accessor :new_key
|
214
42
|
|
215
43
|
# Creates a new LoggedChange instance.
|
216
|
-
# * +
|
44
|
+
# * +loader+: the current LoggedChangeLoader
|
217
45
|
# * +database+: either :+left+ or :+right+
|
218
|
-
def initialize(
|
219
|
-
self.
|
220
|
-
self.database = database
|
46
|
+
def initialize(loader)
|
47
|
+
self.loader = loader
|
221
48
|
self.type = :no_change
|
222
49
|
end
|
223
50
|
|
@@ -281,7 +108,7 @@ module RR
|
|
281
108
|
end.join(key_sep)
|
282
109
|
current_key = org_key
|
283
110
|
|
284
|
-
while change =
|
111
|
+
while change = loader.load(table, current_key)
|
285
112
|
|
286
113
|
new_type = change['change_type']
|
287
114
|
current_type = TYPE_CHANGES["#{current_type}#{new_type}"]
|
@@ -313,16 +140,10 @@ module RR
|
|
313
140
|
load
|
314
141
|
end
|
315
142
|
|
316
|
-
# Returns the time of the oldest change. Returns +nil+ if there are no
|
317
|
-
# changes left.
|
318
|
-
def oldest_change_time
|
319
|
-
session.change_loader(database).oldest_change_time
|
320
|
-
end
|
321
|
-
|
322
143
|
# Loads the oldest available change
|
323
144
|
def load_oldest
|
324
145
|
begin
|
325
|
-
change =
|
146
|
+
change = loader.oldest_change
|
326
147
|
break unless change
|
327
148
|
self.key = key_to_hash(change['change_key'])
|
328
149
|
self.table = change['change_table']
|
@@ -332,7 +153,7 @@ module RR
|
|
332
153
|
|
333
154
|
# Prevents session from going into YAML output
|
334
155
|
def to_yaml_properties
|
335
|
-
instance_variables.sort.reject {|var_name|
|
156
|
+
instance_variables.sort.reject {|var_name| ['@session', '@loader'].include? var_name}
|
336
157
|
end
|
337
158
|
|
338
159
|
end
|
@@ -0,0 +1,196 @@
|
|
1
|
+
module RR
|
2
|
+
|
3
|
+
# Makes management of logged change loaders easier
|
4
|
+
class LoggedChangeLoaders
|
5
|
+
|
6
|
+
# The current Session
|
7
|
+
attr_accessor :session
|
8
|
+
|
9
|
+
# A hash of LoggedChangeLoader instances for the :+left+ and :+right+ database
|
10
|
+
attr_accessor :loaders
|
11
|
+
|
12
|
+
# Create new logged change loaders.
|
13
|
+
# * +session+: Current Session
|
14
|
+
def initialize(session)
|
15
|
+
self.session = session
|
16
|
+
self.loaders = {}
|
17
|
+
[:left, :right].each do |database|
|
18
|
+
loaders[database] = LoggedChangeLoader.new(session, database)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns the LoggedChangeLoader for the specified (:+left+ or :+right+)
|
23
|
+
# database.
|
24
|
+
def [](database)
|
25
|
+
loaders[database]
|
26
|
+
end
|
27
|
+
|
28
|
+
# Forces an update of the change log cache
|
29
|
+
def update
|
30
|
+
[:left, :right].each {|database| self[database].update :forced => true}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Caches the entries in the change log table
|
35
|
+
class LoggedChangeLoader
|
36
|
+
|
37
|
+
# The current +Session+.
|
38
|
+
attr_accessor :session
|
39
|
+
|
40
|
+
# Current database (either :+left+ or :+right+)
|
41
|
+
attr_accessor :database
|
42
|
+
|
43
|
+
# The current +ProxyConnection+.
|
44
|
+
attr_accessor :connection
|
45
|
+
|
46
|
+
# Index to the next unprocessed change in the +change_array+.
|
47
|
+
attr_accessor :current_index
|
48
|
+
|
49
|
+
# ID of the last cached change log record.
|
50
|
+
attr_accessor :current_id
|
51
|
+
|
52
|
+
# Array with all cached changes.
|
53
|
+
# Processed change log records are replaced with +nil+.
|
54
|
+
attr_accessor :change_array
|
55
|
+
|
56
|
+
# Tree (hash) structure for fast access to all cached changes.
|
57
|
+
# First level of tree:
|
58
|
+
# * key: table name
|
59
|
+
# * value: 2nd level tree
|
60
|
+
# 2nd level tree:
|
61
|
+
# * key: the change_key value of the according change log records.
|
62
|
+
# * value:
|
63
|
+
# An array of according change log records (column_name => value hash).
|
64
|
+
# Additional entry of each change log hash:
|
65
|
+
# * key: 'array_index'
|
66
|
+
# * value: index to the change log record in +change_array+
|
67
|
+
attr_accessor :change_tree
|
68
|
+
|
69
|
+
# Date of last update of the cache
|
70
|
+
attr_accessor :last_updated
|
71
|
+
|
72
|
+
# Initializes / resets the cache.
|
73
|
+
def init_cache
|
74
|
+
self.change_tree = {}
|
75
|
+
self.change_array = []
|
76
|
+
self.current_index = 0
|
77
|
+
end
|
78
|
+
private :init_cache
|
79
|
+
|
80
|
+
# Create a new change log record cache.
|
81
|
+
# * +session+: The current +Session+
|
82
|
+
# * +database+: Either :+left+ or :+right+
|
83
|
+
def initialize(session, database)
|
84
|
+
self.session = session
|
85
|
+
self.database = database
|
86
|
+
self.connection = session.send(database)
|
87
|
+
|
88
|
+
init_cache
|
89
|
+
self.current_id = -1
|
90
|
+
self.last_updated = 1.year.ago
|
91
|
+
end
|
92
|
+
|
93
|
+
# Updates the cache.
|
94
|
+
# Options is a hash determining when the update is actually executed:
|
95
|
+
# * :+expire_time+: cache is older than the given number of seconds
|
96
|
+
# * :+forced+: if +true+ update the cache even if not yet expired
|
97
|
+
def update(options = {:forced => false, :expire_time => 1})
|
98
|
+
return unless options[:forced] or Time.now - self.last_updated >= options[:expire_time]
|
99
|
+
|
100
|
+
self.last_updated = Time.now
|
101
|
+
|
102
|
+
# First, let's use a LIMIT clause (via :row_buffer_size option) to verify
|
103
|
+
# if there are any pending changes.
|
104
|
+
# (If there are many pending changes, this is (at least with PostgreSQL)
|
105
|
+
# much faster.)
|
106
|
+
cursor = connection.select_cursor(
|
107
|
+
:table => change_log_table,
|
108
|
+
:from => {'id' => current_id},
|
109
|
+
:exclude_starting_row => true,
|
110
|
+
:row_buffer_size => 1
|
111
|
+
)
|
112
|
+
return unless cursor.next?
|
113
|
+
|
114
|
+
# Something is here. Let's actually load it.
|
115
|
+
cursor = connection.select_cursor(
|
116
|
+
:table => change_log_table,
|
117
|
+
:from => {'id' => current_id},
|
118
|
+
:exclude_starting_row => true,
|
119
|
+
:type_cast => true
|
120
|
+
)
|
121
|
+
while cursor.next?
|
122
|
+
change = cursor.next_row
|
123
|
+
self.current_id = change['id']
|
124
|
+
self.change_array << change
|
125
|
+
change['array_index'] = self.change_array.size - 1
|
126
|
+
|
127
|
+
table_change_tree = change_tree[change['change_table']] ||= {}
|
128
|
+
key_changes = table_change_tree[change['change_key']] ||= []
|
129
|
+
key_changes << change
|
130
|
+
end
|
131
|
+
cursor.clear
|
132
|
+
end
|
133
|
+
|
134
|
+
# Returns the creation time of the oldest unprocessed change log record.
|
135
|
+
def oldest_change_time
|
136
|
+
change = oldest_change
|
137
|
+
change['change_time'] if change
|
138
|
+
end
|
139
|
+
|
140
|
+
# Returns the oldest unprocessed change log record (column_name => value hash).
|
141
|
+
def oldest_change
|
142
|
+
update
|
143
|
+
oldest_change = nil
|
144
|
+
unless change_array.empty?
|
145
|
+
while (oldest_change = change_array[self.current_index]) == nil
|
146
|
+
self.current_index += 1
|
147
|
+
end
|
148
|
+
end
|
149
|
+
oldest_change
|
150
|
+
end
|
151
|
+
|
152
|
+
# Returns the specified change log record (column_name => value hash).
|
153
|
+
# * +change_table+: the name of the table that was changed
|
154
|
+
# * +change_key+: the change key of the modified record
|
155
|
+
def load(change_table, change_key)
|
156
|
+
update
|
157
|
+
change = nil
|
158
|
+
table_change_tree = change_tree[change_table]
|
159
|
+
if table_change_tree
|
160
|
+
key_changes = table_change_tree[change_key]
|
161
|
+
if key_changes
|
162
|
+
# get change object and delete from key_changes
|
163
|
+
change = key_changes.shift
|
164
|
+
|
165
|
+
# delete change from change_array
|
166
|
+
change_array[change['array_index']] = nil
|
167
|
+
|
168
|
+
# delete change from database
|
169
|
+
connection.execute "delete from #{change_log_table} where id = #{change['id']}"
|
170
|
+
|
171
|
+
# delete key_changes if empty
|
172
|
+
if key_changes.empty?
|
173
|
+
table_change_tree.delete change_key
|
174
|
+
end
|
175
|
+
|
176
|
+
# delete table_change_tree if empty
|
177
|
+
if table_change_tree.empty?
|
178
|
+
change_tree.delete change_table
|
179
|
+
end
|
180
|
+
|
181
|
+
# reset everything if no more changes remain
|
182
|
+
if change_tree.empty?
|
183
|
+
init_cache
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
change
|
188
|
+
end
|
189
|
+
|
190
|
+
# Returns the name of the change log table
|
191
|
+
def change_log_table
|
192
|
+
@change_log_table ||= "#{session.configuration.options[:rep_prefix]}_pending_changes"
|
193
|
+
end
|
194
|
+
private :change_log_table
|
195
|
+
end
|
196
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module RR
|
2
|
+
|
3
|
+
# Wraps an existing cursor.
|
4
|
+
# Purpose: send regular updates to the installed TaskSweeper
|
5
|
+
class NoisyCursor
|
6
|
+
# The original cusor
|
7
|
+
attr_accessor :org_cursor
|
8
|
+
|
9
|
+
# The installed task sweeper
|
10
|
+
attr_accessor :sweeper
|
11
|
+
|
12
|
+
# Create a new NoisyCursor.
|
13
|
+
# * cursor: the original cursor
|
14
|
+
# * sweeper: the target TaskSweeper
|
15
|
+
def initialize(cursor, sweeper)
|
16
|
+
self.org_cursor = cursor
|
17
|
+
self.sweeper = sweeper
|
18
|
+
end
|
19
|
+
|
20
|
+
# Delegate the uninteresting methods to the original cursor
|
21
|
+
def next?; org_cursor.next? end
|
22
|
+
def clear; org_cursor.clear end
|
23
|
+
|
24
|
+
# Returns the row as a column => value hash and moves the cursor to the next row.
|
25
|
+
def next_row
|
26
|
+
sweeper.ping
|
27
|
+
row = org_cursor.next_row
|
28
|
+
sweeper.ping
|
29
|
+
row
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Modifies ProxyConnections to send regular pings to an installed TaskSweeper
|
34
|
+
module NoisyConnection
|
35
|
+
|
36
|
+
# The installed TaskSweeper
|
37
|
+
attr_accessor :sweeper
|
38
|
+
|
39
|
+
# Modifies ProxyConnection#select_cursor to wrap the returned cursor
|
40
|
+
# into a NoisyCursor.
|
41
|
+
def select_cursor(options)
|
42
|
+
sweeper.ping
|
43
|
+
org_cursor = super
|
44
|
+
sweeper.ping
|
45
|
+
NoisyCursor.new(org_cursor, sweeper)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Wraps ProxyConnection#insert_record to update the TaskSweeper
|
49
|
+
def insert_record(table, values)
|
50
|
+
sweeper.ping
|
51
|
+
result = super
|
52
|
+
sweeper.ping
|
53
|
+
result
|
54
|
+
end
|
55
|
+
|
56
|
+
# Wraps ProxyConnection#update_record to update the TaskSweeper
|
57
|
+
def update_record(table, values, org_key = nil)
|
58
|
+
sweeper.ping
|
59
|
+
result = super
|
60
|
+
sweeper.ping
|
61
|
+
result
|
62
|
+
end
|
63
|
+
|
64
|
+
# Wraps ProxyConnection#delete_record to update the TaskSweeper
|
65
|
+
def delete_record(table, values)
|
66
|
+
sweeper.ping
|
67
|
+
result = super
|
68
|
+
sweeper.ping
|
69
|
+
result
|
70
|
+
end
|
71
|
+
|
72
|
+
# Wraps ProxyConnection#commit_db_transaction to update the TaskSweeper
|
73
|
+
def commit_db_transaction
|
74
|
+
sweeper.ping
|
75
|
+
result = super
|
76
|
+
sweeper.ping
|
77
|
+
result
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -198,21 +198,14 @@ module RR
|
|
198
198
|
self.manual_primary_keys = {}
|
199
199
|
end
|
200
200
|
|
201
|
-
# Checks if the connection is still active and if not, reestablished it.
|
202
|
-
def refresh
|
203
|
-
unless self.connection.active?
|
204
|
-
self.connection = ConnectionExtenders.db_connect config
|
205
|
-
end
|
206
|
-
end
|
207
|
-
|
208
201
|
# Destroys the session
|
209
202
|
def destroy
|
210
|
-
self.connection.disconnect!
|
211
|
-
|
212
203
|
cursors.each_key do |cursor|
|
213
204
|
cursor.destroy
|
214
205
|
end
|
215
206
|
cursors.clear
|
207
|
+
|
208
|
+
self.connection.disconnect!
|
216
209
|
end
|
217
210
|
|
218
211
|
# Quotes the given value. It is assumed that the value belongs to the specified column name and table name.
|
@@ -5,7 +5,12 @@ module RR
|
|
5
5
|
class ReplicationDifference
|
6
6
|
|
7
7
|
# The current Session.
|
8
|
-
|
8
|
+
def session
|
9
|
+
@session ||= loaders.session
|
10
|
+
end
|
11
|
+
|
12
|
+
# The current LoggedChangeLoaders instance
|
13
|
+
attr_accessor :loaders
|
9
14
|
|
10
15
|
# The type of the difference. Either
|
11
16
|
# * :+left+: change in left database
|
@@ -21,9 +26,9 @@ module RR
|
|
21
26
|
end
|
22
27
|
|
23
28
|
# Creates a new ReplicationDifference instance.
|
24
|
-
# +
|
25
|
-
def initialize(
|
26
|
-
self.
|
29
|
+
# +loaders+ is teh current LoggedChangeLoaders instance
|
30
|
+
def initialize(loaders)
|
31
|
+
self.loaders = loaders
|
27
32
|
end
|
28
33
|
|
29
34
|
# Should be set to +true+ if this ReplicationDifference instance was
|
@@ -52,7 +57,7 @@ module RR
|
|
52
57
|
|
53
58
|
# Amends a difference according to new entries in the change log table
|
54
59
|
def amend
|
55
|
-
|
60
|
+
loaders.update
|
56
61
|
changes[:left].load
|
57
62
|
changes[:right].load
|
58
63
|
self.type = DIFF_TYPES[changes[:left].type][changes[:right].type]
|
@@ -62,8 +67,8 @@ module RR
|
|
62
67
|
def load
|
63
68
|
change_times = {}
|
64
69
|
[:left, :right].each do |database|
|
65
|
-
changes[database] = LoggedChange.new
|
66
|
-
change_times[database] =
|
70
|
+
changes[database] = LoggedChange.new loaders[database]
|
71
|
+
change_times[database] = loaders[database].oldest_change_time
|
67
72
|
end
|
68
73
|
return if change_times[:left] == nil and change_times[:right] == nil
|
69
74
|
|
@@ -82,9 +87,9 @@ module RR
|
|
82
87
|
self.loaded = true
|
83
88
|
end
|
84
89
|
|
85
|
-
# Prevents session from going into YAML output
|
90
|
+
# Prevents session and change loaders from going into YAML output
|
86
91
|
def to_yaml_properties
|
87
|
-
instance_variables.sort.reject {|var_name|
|
92
|
+
instance_variables.sort.reject {|var_name| ['@session', '@loaders'].include? var_name}
|
88
93
|
end
|
89
94
|
|
90
95
|
end
|
@@ -262,6 +262,13 @@ module RR
|
|
262
262
|
end
|
263
263
|
end
|
264
264
|
|
265
|
+
# Calls the potentially provided :+after_init+ handler after infrastructure
|
266
|
+
# tables are created.
|
267
|
+
def call_after_infrastructure_setup_handler
|
268
|
+
handler = session.configuration.options[:after_infrastructure_setup]
|
269
|
+
handler.call(session) if handler
|
270
|
+
end
|
271
|
+
|
265
272
|
# Prepares the database / tables for replication.
|
266
273
|
def prepare_replication
|
267
274
|
exclude_rubyrep_tables
|
@@ -269,6 +276,8 @@ module RR
|
|
269
276
|
puts "Verifying RubyRep tables"
|
270
277
|
ensure_infrastructure
|
271
278
|
|
279
|
+
call_after_infrastructure_setup_handler
|
280
|
+
|
272
281
|
puts "Checking for and removing rubyrep triggers from unconfigured tables"
|
273
282
|
restore_unconfigured_tables
|
274
283
|
|