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
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
|
|