rubyrep 1.0.0
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 +4 -0
- data/License.txt +20 -0
- data/Manifest.txt +137 -0
- data/README.txt +37 -0
- data/Rakefile +30 -0
- data/bin/rubyrep +8 -0
- data/config/hoe.rb +72 -0
- data/config/mysql_config.rb +25 -0
- data/config/postgres_config.rb +21 -0
- data/config/proxied_test_config.rb +14 -0
- data/config/redmine_config.rb +17 -0
- data/config/rep_config.rb +20 -0
- data/config/requirements.rb +32 -0
- data/config/test_config.rb +20 -0
- data/lib/rubyrep/base_runner.rb +195 -0
- data/lib/rubyrep/command_runner.rb +144 -0
- data/lib/rubyrep/committers/buffered_committer.rb +140 -0
- data/lib/rubyrep/committers/committers.rb +146 -0
- data/lib/rubyrep/configuration.rb +240 -0
- data/lib/rubyrep/connection_extenders/connection_extenders.rb +133 -0
- data/lib/rubyrep/connection_extenders/jdbc_extender.rb +284 -0
- data/lib/rubyrep/connection_extenders/mysql_extender.rb +168 -0
- data/lib/rubyrep/connection_extenders/postgresql_extender.rb +261 -0
- data/lib/rubyrep/database_proxy.rb +52 -0
- data/lib/rubyrep/direct_table_scan.rb +75 -0
- data/lib/rubyrep/generate_runner.rb +105 -0
- data/lib/rubyrep/initializer.rb +39 -0
- data/lib/rubyrep/logged_change.rb +326 -0
- data/lib/rubyrep/proxied_table_scan.rb +171 -0
- data/lib/rubyrep/proxy_block_cursor.rb +145 -0
- data/lib/rubyrep/proxy_connection.rb +318 -0
- data/lib/rubyrep/proxy_cursor.rb +44 -0
- data/lib/rubyrep/proxy_row_cursor.rb +43 -0
- data/lib/rubyrep/proxy_runner.rb +89 -0
- data/lib/rubyrep/replication_difference.rb +91 -0
- data/lib/rubyrep/replication_extenders/mysql_replication.rb +271 -0
- data/lib/rubyrep/replication_extenders/postgresql_replication.rb +204 -0
- data/lib/rubyrep/replication_extenders/replication_extenders.rb +26 -0
- data/lib/rubyrep/replication_helper.rb +104 -0
- data/lib/rubyrep/replication_initializer.rb +307 -0
- data/lib/rubyrep/replication_run.rb +48 -0
- data/lib/rubyrep/replication_runner.rb +138 -0
- data/lib/rubyrep/replicators/replicators.rb +37 -0
- data/lib/rubyrep/replicators/two_way_replicator.rb +334 -0
- data/lib/rubyrep/scan_progress_printers/progress_bar.rb +65 -0
- data/lib/rubyrep/scan_progress_printers/scan_progress_printers.rb +65 -0
- data/lib/rubyrep/scan_report_printers/scan_detail_reporter.rb +111 -0
- data/lib/rubyrep/scan_report_printers/scan_report_printers.rb +67 -0
- data/lib/rubyrep/scan_report_printers/scan_summary_reporter.rb +75 -0
- data/lib/rubyrep/scan_runner.rb +25 -0
- data/lib/rubyrep/session.rb +177 -0
- data/lib/rubyrep/sync_helper.rb +111 -0
- data/lib/rubyrep/sync_runner.rb +31 -0
- data/lib/rubyrep/syncers/syncers.rb +112 -0
- data/lib/rubyrep/syncers/two_way_syncer.rb +174 -0
- data/lib/rubyrep/table_scan.rb +54 -0
- data/lib/rubyrep/table_scan_helper.rb +38 -0
- data/lib/rubyrep/table_sorter.rb +70 -0
- data/lib/rubyrep/table_spec_resolver.rb +136 -0
- data/lib/rubyrep/table_sync.rb +68 -0
- data/lib/rubyrep/trigger_mode_switcher.rb +63 -0
- data/lib/rubyrep/type_casting_cursor.rb +31 -0
- data/lib/rubyrep/uninstall_runner.rb +92 -0
- data/lib/rubyrep/version.rb +9 -0
- data/lib/rubyrep.rb +68 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/script/txt2html +74 -0
- data/setup.rb +1585 -0
- data/sims/performance/big_rep_spec.rb +100 -0
- data/sims/performance/big_scan_spec.rb +57 -0
- data/sims/performance/big_sync_spec.rb +141 -0
- data/sims/performance/performance.rake +228 -0
- data/sims/sim_helper.rb +24 -0
- data/spec/base_runner_spec.rb +218 -0
- data/spec/buffered_committer_spec.rb +271 -0
- data/spec/command_runner_spec.rb +145 -0
- data/spec/committers_spec.rb +174 -0
- data/spec/configuration_spec.rb +198 -0
- data/spec/connection_extender_interface_spec.rb +138 -0
- data/spec/connection_extenders_registration_spec.rb +129 -0
- data/spec/database_proxy_spec.rb +48 -0
- data/spec/database_rake_spec.rb +40 -0
- data/spec/db_specific_connection_extenders_spec.rb +34 -0
- data/spec/db_specific_replication_extenders_spec.rb +38 -0
- data/spec/direct_table_scan_spec.rb +61 -0
- data/spec/generate_runner_spec.rb +84 -0
- data/spec/initializer_spec.rb +46 -0
- data/spec/logged_change_spec.rb +480 -0
- data/spec/postgresql_replication_spec.rb +48 -0
- data/spec/postgresql_support_spec.rb +57 -0
- data/spec/progress_bar_spec.rb +77 -0
- data/spec/proxied_table_scan_spec.rb +151 -0
- data/spec/proxy_block_cursor_spec.rb +197 -0
- data/spec/proxy_connection_spec.rb +399 -0
- data/spec/proxy_cursor_spec.rb +56 -0
- data/spec/proxy_row_cursor_spec.rb +66 -0
- data/spec/proxy_runner_spec.rb +70 -0
- data/spec/replication_difference_spec.rb +160 -0
- data/spec/replication_extender_interface_spec.rb +365 -0
- data/spec/replication_extenders_spec.rb +32 -0
- data/spec/replication_helper_spec.rb +121 -0
- data/spec/replication_initializer_spec.rb +477 -0
- data/spec/replication_run_spec.rb +166 -0
- data/spec/replication_runner_spec.rb +213 -0
- data/spec/replicators_spec.rb +31 -0
- data/spec/rubyrep_spec.rb +8 -0
- data/spec/scan_detail_reporter_spec.rb +119 -0
- data/spec/scan_progress_printers_spec.rb +68 -0
- data/spec/scan_report_printers_spec.rb +67 -0
- data/spec/scan_runner_spec.rb +50 -0
- data/spec/scan_summary_reporter_spec.rb +61 -0
- data/spec/session_spec.rb +212 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +295 -0
- data/spec/sync_helper_spec.rb +157 -0
- data/spec/sync_runner_spec.rb +78 -0
- data/spec/syncers_spec.rb +171 -0
- data/spec/table_scan_helper_spec.rb +29 -0
- data/spec/table_scan_spec.rb +49 -0
- data/spec/table_sorter_spec.rb +31 -0
- data/spec/table_spec_resolver_spec.rb +102 -0
- data/spec/table_sync_spec.rb +84 -0
- data/spec/trigger_mode_switcher_spec.rb +83 -0
- data/spec/two_way_replicator_spec.rb +551 -0
- data/spec/two_way_syncer_spec.rb +256 -0
- data/spec/type_casting_cursor_spec.rb +50 -0
- data/spec/uninstall_runner_spec.rb +86 -0
- data/tasks/database.rake +439 -0
- data/tasks/deployment.rake +29 -0
- data/tasks/environment.rake +9 -0
- data/tasks/java.rake +37 -0
- data/tasks/redmine_test.rake +47 -0
- data/tasks/rspec.rake +68 -0
- data/tasks/rubyrep.tailor +18 -0
- data/tasks/stats.rake +19 -0
- data/tasks/task_helper.rb +20 -0
- data.tar.gz.sig +0 -0
- metadata +243 -0
- metadata.gz.sig +0 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
module RR::ScanReportPrinters
|
|
2
|
+
# A ScanReportPrinter producing a summary (number of differences) only.
|
|
3
|
+
class ScanSummaryReporter
|
|
4
|
+
|
|
5
|
+
# Register ScanSummaryReporter with the given command line options.
|
|
6
|
+
# (Command line format as specified by OptionParser#on.)
|
|
7
|
+
RR::ScanReportPrinters.register self, "-s", "--summary[=detailed]",
|
|
8
|
+
"Print the number of differences of each table. Either totals only, e. g.",
|
|
9
|
+
" left_table / right_table [differences]",
|
|
10
|
+
"or a detailed split by type, e. g.",
|
|
11
|
+
" left_table / right_table [conflicts] [left_only records] [right_only records]"
|
|
12
|
+
|
|
13
|
+
# Set to true if only the total number of differences should be reported
|
|
14
|
+
attr_accessor :only_totals
|
|
15
|
+
|
|
16
|
+
# Name of the left table of the current scan
|
|
17
|
+
attr_accessor :left_table
|
|
18
|
+
|
|
19
|
+
# Name of the right table of the current scan
|
|
20
|
+
attr_accessor :right_table
|
|
21
|
+
|
|
22
|
+
# Hold the result of the current scan. A hash with a running count of
|
|
23
|
+
# +:conflict+, +:left+ (only) or +:right+ (only) records.
|
|
24
|
+
attr_accessor :scan_result
|
|
25
|
+
|
|
26
|
+
# A scan run is to be started using this scan result printer.
|
|
27
|
+
# +arg+ is the command line argument as yielded by OptionParser#on.
|
|
28
|
+
def initialize(_, arg)
|
|
29
|
+
self.only_totals = (arg != 'detailed')
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# A scan of the given 'left' table and corresponding 'right' table is executed.
|
|
33
|
+
# Needs to yield so that the actual scan can be executed.
|
|
34
|
+
def scan(left_table, right_table)
|
|
35
|
+
self.left_table = left_table
|
|
36
|
+
self.right_table = right_table
|
|
37
|
+
self.scan_result = {:conflict => 0, :left => 0, :right => 0}
|
|
38
|
+
|
|
39
|
+
header = left_table.clone
|
|
40
|
+
header << " / " << right_table if left_table != right_table
|
|
41
|
+
$stdout.write "#{header.rjust(36)} "
|
|
42
|
+
|
|
43
|
+
yield # Give control back so that the actual table scan can be done.
|
|
44
|
+
|
|
45
|
+
if only_totals
|
|
46
|
+
$stdout.write \
|
|
47
|
+
"#{rjust_value(scan_result[:conflict] + scan_result[:left] + scan_result[:right])}"
|
|
48
|
+
else
|
|
49
|
+
$stdout.write \
|
|
50
|
+
"#{rjust_value(scan_result[:conflict])} " +
|
|
51
|
+
"#{rjust_value(scan_result[:left])} " +
|
|
52
|
+
"#{rjust_value(scan_result[:right])}"
|
|
53
|
+
end
|
|
54
|
+
$stdout.puts
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Right adjusts the given number and returns according string.
|
|
58
|
+
def rjust_value(value)
|
|
59
|
+
value.to_s.rjust(3)
|
|
60
|
+
end
|
|
61
|
+
private :rjust_value
|
|
62
|
+
|
|
63
|
+
# Each difference is handed to the printer as described in the format
|
|
64
|
+
# as described e. g. in DirectTableScan#run
|
|
65
|
+
def report_difference(type, row)
|
|
66
|
+
scan_result[type] += 1
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Optional method. If a scan report printer has it, it is called after the
|
|
70
|
+
# last table scan is executed.
|
|
71
|
+
# (A good place to print a final summary.)
|
|
72
|
+
def scanning_finished
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
|
|
2
|
+
|
|
3
|
+
module RR
|
|
4
|
+
# This class implements the functionality of the rrscan.rb command.
|
|
5
|
+
class ScanRunner < BaseRunner
|
|
6
|
+
|
|
7
|
+
CommandRunner.register 'scan' => {
|
|
8
|
+
:command => self,
|
|
9
|
+
:description => 'Scans for differing records between databases'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
# Returns summary description string for the scan command.
|
|
13
|
+
def summary_description
|
|
14
|
+
"Scans for differences of the specified tables between both databases."
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Creates the correct scan class.
|
|
18
|
+
# Parameters as defined under BaseRunner#create_processor
|
|
19
|
+
def create_processor(left_table, right_table)
|
|
20
|
+
TableScanHelper.scan_class(session).new session, left_table, right_table
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
require 'drb'
|
|
2
|
+
|
|
3
|
+
module RR
|
|
4
|
+
|
|
5
|
+
# This class represents a rubyrep session.
|
|
6
|
+
# Creates and holds expensive objects like e. g. database connections.
|
|
7
|
+
class Session
|
|
8
|
+
|
|
9
|
+
# The Configuration object provided to the initializer
|
|
10
|
+
attr_accessor :configuration
|
|
11
|
+
|
|
12
|
+
# Returns the "left" ActiveRecord / proxy database connection
|
|
13
|
+
def left
|
|
14
|
+
@connections[:left]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Stores the "left" ActiveRecord /proxy database connection
|
|
18
|
+
def left=(connection)
|
|
19
|
+
@connections[:left] = connection
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Returns the "right" ActiveRecord / proxy database connection
|
|
23
|
+
def right
|
|
24
|
+
@connections[:right]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Stores the "right" ActiveRecord / proxy database connection
|
|
28
|
+
def right=(connection)
|
|
29
|
+
@connections[:right] = connection
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Hash to hold under either :left or :right the according Drb / direct DatabaseProxy
|
|
33
|
+
attr_accessor :proxies
|
|
34
|
+
|
|
35
|
+
# Creates a hash of manual primary key names as can be specified with the
|
|
36
|
+
# Configuration options :+primary_key_names+ or :+auto_key_limit+.
|
|
37
|
+
# * +db_arm: should be either :left or :right
|
|
38
|
+
#
|
|
39
|
+
# Returns the identified manual primary keys. This is a hash with
|
|
40
|
+
# * key: table_name
|
|
41
|
+
# * value: array of primary key names
|
|
42
|
+
def manual_primary_keys(db_arm)
|
|
43
|
+
manual_primary_keys = {}
|
|
44
|
+
resolver = TableSpecResolver.new self
|
|
45
|
+
table_pairs = resolver.resolve configuration.included_table_specs, [], false
|
|
46
|
+
table_pairs.each do |table_pair|
|
|
47
|
+
options = configuration.options_for_table(table_pair[:left])
|
|
48
|
+
key_names = options[:key]
|
|
49
|
+
if key_names == nil and options[:auto_key_limit] > 0
|
|
50
|
+
if left.primary_key_names(table_pair[:left], :raw => true).empty?
|
|
51
|
+
column_names = left.column_names(table_pair[:left])
|
|
52
|
+
if column_names.size <= options[:auto_key_limit]
|
|
53
|
+
key_names = column_names
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
if key_names
|
|
58
|
+
table_name = table_pair[db_arm]
|
|
59
|
+
manual_primary_keys[table_name] = [key_names].flatten
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
manual_primary_keys
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Returns the corresponding table in the other database.
|
|
66
|
+
# * +db_arm+: database of the given table (either :+left+ or :+right+)
|
|
67
|
+
# * +table+: name of the table
|
|
68
|
+
#
|
|
69
|
+
# If no corresponding table can be found, return the given table.
|
|
70
|
+
# Rationale:
|
|
71
|
+
# Support the case where a table was dropped from the configuration but
|
|
72
|
+
# there were still some unreplicated changes left.
|
|
73
|
+
def corresponding_table(db_arm, table)
|
|
74
|
+
unless @table_map
|
|
75
|
+
@table_map = {:left => {}, :right => {}}
|
|
76
|
+
resolver = TableSpecResolver.new self
|
|
77
|
+
table_pairs = resolver.resolve configuration.included_table_specs, [], false
|
|
78
|
+
table_pairs.each do |table_pair|
|
|
79
|
+
@table_map[:left][table_pair[:left]] = table_pair[:right]
|
|
80
|
+
@table_map[:right][table_pair[:right]] = table_pair[:left]
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
@table_map[db_arm][table] || table
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Does the actual work of establishing a database connection
|
|
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
|
|
112
|
+
def proxied?
|
|
113
|
+
[configuration.left, configuration.right].any? \
|
|
114
|
+
{|arm_config| arm_config.include? :proxy_host}
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Returns an array of table pairs of the configured tables.
|
|
118
|
+
# Refer to TableSpecResolver#resolve for a detailed description of the
|
|
119
|
+
# return value.
|
|
120
|
+
# If +included_table_specs+ is provided (that is: not an empty array), it
|
|
121
|
+
# will be used instead of the configured table specs.
|
|
122
|
+
def configured_table_pairs(included_table_specs = [])
|
|
123
|
+
resolver = TableSpecResolver.new self
|
|
124
|
+
included_table_specs = configuration.included_table_specs if included_table_specs.empty?
|
|
125
|
+
resolver.resolve included_table_specs, configuration.excluded_table_specs
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Orders the array of table pairs as per primary key / foreign key relations
|
|
129
|
+
# of the tables. Returns the result.
|
|
130
|
+
# Only sorts if the configuration has set option :+table_ordering+.
|
|
131
|
+
# Refer to TableSpecResolver#resolve for a detailed description of the
|
|
132
|
+
# parameter and return value.
|
|
133
|
+
def sort_table_pairs(table_pairs)
|
|
134
|
+
if configuration.options[:table_ordering]
|
|
135
|
+
left_tables = table_pairs.map {|table_pair| table_pair[:left]}
|
|
136
|
+
sorted_left_tables = TableSorter.new(self, left_tables).sort
|
|
137
|
+
sorted_left_tables.map do |left_table|
|
|
138
|
+
table_pairs.find do |table_pair|
|
|
139
|
+
table_pair[:left] == left_table
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
else
|
|
143
|
+
table_pairs
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Refreshes (reestablish if no more active) the database connections.
|
|
148
|
+
def refresh
|
|
149
|
+
[:left, :right].each {|database| send(database).refresh}
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Creates a new rubyrep session with the provided Configuration
|
|
153
|
+
def initialize(config = Initializer::configuration)
|
|
154
|
+
@connections = {:left => nil, :right => nil}
|
|
155
|
+
@proxies = {:left => nil, :right => nil}
|
|
156
|
+
|
|
157
|
+
# Keep the database configuration for future reference
|
|
158
|
+
self.configuration = config
|
|
159
|
+
|
|
160
|
+
# Determine method of connection (either 'proxy_connect' or 'db_connect'
|
|
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
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
module RR
|
|
2
|
+
|
|
3
|
+
# Provides helper functionality for the table syncers.
|
|
4
|
+
# The methods exposed by this class are intended to provide a stable interface
|
|
5
|
+
# for third party syncers.
|
|
6
|
+
class SyncHelper
|
|
7
|
+
|
|
8
|
+
# The current +TableSync+ instance
|
|
9
|
+
attr_accessor :table_sync
|
|
10
|
+
|
|
11
|
+
# The active +Session+
|
|
12
|
+
def session; table_sync.session; end
|
|
13
|
+
|
|
14
|
+
# Name of the left table
|
|
15
|
+
def left_table; table_sync.left_table; end
|
|
16
|
+
|
|
17
|
+
# Name of the right table
|
|
18
|
+
def right_table; table_sync.right_table; end
|
|
19
|
+
|
|
20
|
+
# A hash with
|
|
21
|
+
# :+left+: name of the table in the left database
|
|
22
|
+
# :+right+: name of the table in the right database
|
|
23
|
+
def tables
|
|
24
|
+
@tables ||= {:left => left_table, :right => right_table}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Sync options for the current table sync
|
|
28
|
+
def sync_options; @sync_options ||= table_sync.sync_options; end
|
|
29
|
+
|
|
30
|
+
# Delegates to Committer#insert_record
|
|
31
|
+
def insert_record(database, values)
|
|
32
|
+
committer.insert_record(database, tables[database], values)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Delegates to Committer#insert_record
|
|
36
|
+
def update_record(database, values, old_key = nil)
|
|
37
|
+
committer.update_record(database, tables[database], values, old_key)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Delegates to Committer#insert_record
|
|
41
|
+
def delete_record(database, values)
|
|
42
|
+
committer.delete_record(database, tables[database], values)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Return the committer, creating it if not yet there.
|
|
46
|
+
def committer
|
|
47
|
+
unless @committer
|
|
48
|
+
committer_class = Committers::committers[sync_options[:committer]]
|
|
49
|
+
@committer = committer_class.new(session)
|
|
50
|
+
end
|
|
51
|
+
@committer
|
|
52
|
+
end
|
|
53
|
+
private :committer
|
|
54
|
+
|
|
55
|
+
# Checks if the event log table already exists and creates it if necessary
|
|
56
|
+
def ensure_event_log
|
|
57
|
+
unless @ensured_event_log
|
|
58
|
+
ReplicationInitializer.new(session).ensure_event_log
|
|
59
|
+
@ensured_event_log = true
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Returns an array of primary key names for the synced table
|
|
64
|
+
def primary_key_names
|
|
65
|
+
@primary_key_names ||= session.left.primary_key_names(left_table)
|
|
66
|
+
end
|
|
67
|
+
private :primary_key_names
|
|
68
|
+
|
|
69
|
+
# Logs the outcome of a replication into the replication log table.
|
|
70
|
+
# * +row+: a column_name => value hash for at least the primary keys of the record
|
|
71
|
+
# * +type+: string describing the type of the sync
|
|
72
|
+
# * +outcome+: string describing what's done about the sync
|
|
73
|
+
# * +details+: string with further details regarding the sync
|
|
74
|
+
def log_sync_outcome(row, type, outcome, details = nil)
|
|
75
|
+
ensure_event_log
|
|
76
|
+
if primary_key_names.size == 1
|
|
77
|
+
key = row[primary_key_names[0]]
|
|
78
|
+
else
|
|
79
|
+
key_parts = primary_key_names.map do |column_name|
|
|
80
|
+
%Q("#{column_name}"=>#{row[column_name].to_s.inspect})
|
|
81
|
+
end
|
|
82
|
+
key = key_parts.join(', ')
|
|
83
|
+
end
|
|
84
|
+
sync_details = details == nil ? nil : details[0...ReplicationInitializer::LONG_DESCRIPTION_SIZE]
|
|
85
|
+
|
|
86
|
+
session.left.insert_record "#{sync_options[:rep_prefix]}_logged_events", {
|
|
87
|
+
:activity => 'sync',
|
|
88
|
+
:change_table => left_table,
|
|
89
|
+
:diff_type => type.to_s,
|
|
90
|
+
:change_key => key,
|
|
91
|
+
:left_change_type => nil,
|
|
92
|
+
:right_change_type => nil,
|
|
93
|
+
:description => outcome.to_s,
|
|
94
|
+
:long_description => sync_details,
|
|
95
|
+
:event_time => Time.now,
|
|
96
|
+
:diff_dump => nil
|
|
97
|
+
}
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Asks the committer (if it exists) to finalize any open transactions
|
|
101
|
+
# +success+ should be true if there were no problems, false otherwise.
|
|
102
|
+
def finalize(success = true)
|
|
103
|
+
@committer.finalize(success) if @committer
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Creates a new SyncHelper for the given +TableSync+ instance.
|
|
107
|
+
def initialize(table_sync)
|
|
108
|
+
self.table_sync = table_sync
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
|
|
2
|
+
|
|
3
|
+
module RR
|
|
4
|
+
# This class implements the functionality of the rrsync.rb command.
|
|
5
|
+
class SyncRunner < BaseRunner
|
|
6
|
+
|
|
7
|
+
CommandRunner.register 'sync' => {
|
|
8
|
+
:command => self,
|
|
9
|
+
:description => 'Syncs records between databases'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
# Returns summary description string for the scan command.
|
|
13
|
+
def summary_description
|
|
14
|
+
"Syncs the differences of the specified tables between both databases."
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Creates the correct scan class.
|
|
18
|
+
# Parameters as defined under BaseRunner#create_processor
|
|
19
|
+
def create_processor(left_table, right_table)
|
|
20
|
+
TableSync.new session, left_table, right_table
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Reorders the table pairs to avoid foreign key conflicts.
|
|
24
|
+
# More information on this methods at BaseRunner#prepare_table_pairs.
|
|
25
|
+
def prepare_table_pairs(table_pairs)
|
|
26
|
+
session.sort_table_pairs(table_pairs)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
module RR
|
|
2
|
+
# Syncers are classes that implement the sync policies.
|
|
3
|
+
# This module provides functionality to register syncers and access the
|
|
4
|
+
# list of registered syncers.
|
|
5
|
+
# Each Syncer must register itself with Syncers#register.
|
|
6
|
+
# Each Syncer must implement the following methods:
|
|
7
|
+
#
|
|
8
|
+
# # Creates a new syncer (A syncer is used for one table sync only)
|
|
9
|
+
# # * sync_helper: a SyncHelper object providing necessary information and functionalities
|
|
10
|
+
# def initialize(sync_helper)
|
|
11
|
+
#
|
|
12
|
+
# # Called to sync the provided difference.
|
|
13
|
+
# # See DirectTableScan#run for a description of the +type+ and +row+ parameters.
|
|
14
|
+
# def sync_difference(type, row)
|
|
15
|
+
#
|
|
16
|
+
# # Provides default option for the syncer. Optional.
|
|
17
|
+
# # Returns a hash with :key => value pairs.
|
|
18
|
+
# def self.default_options
|
|
19
|
+
module Syncers
|
|
20
|
+
# Returns a Hash of currently registered syncers.
|
|
21
|
+
# (Empty Hash if no syncers were defined.)
|
|
22
|
+
def self.syncers
|
|
23
|
+
@syncers ||= {}
|
|
24
|
+
@syncers
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Registers one or multiple syncers.
|
|
28
|
+
# syncer_hash is a Hash with
|
|
29
|
+
# key:: The adapter symbol as used to reference the syncer
|
|
30
|
+
# value:: The class implementing the syncer
|
|
31
|
+
def self.register(syncer_hash)
|
|
32
|
+
@syncers ||= {}
|
|
33
|
+
@syncers.merge! syncer_hash
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Returns the correct syncer class as per provided options hash
|
|
37
|
+
def self.configured_syncer(options)
|
|
38
|
+
syncer_id = options[:syncer]
|
|
39
|
+
syncer_id ||= options[:replicator]
|
|
40
|
+
syncers[syncer_id]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# This syncer implements a one way sync.
|
|
44
|
+
# Syncer options relevant for this syncer:
|
|
45
|
+
# * +:direction+: Sync direction. Possible values:
|
|
46
|
+
# * +:left+
|
|
47
|
+
# * +:right+
|
|
48
|
+
# * +:delete+: Default: false. If true, deletes in the target database all
|
|
49
|
+
# records _not_ existing in the source database.
|
|
50
|
+
# * +:update+: If true (default), update records in the target database
|
|
51
|
+
# if different.
|
|
52
|
+
# * +:insert+: If true (default), copy over records not existing in the
|
|
53
|
+
# target database.
|
|
54
|
+
class OneWaySyncer
|
|
55
|
+
|
|
56
|
+
# Register the syncer
|
|
57
|
+
Syncers.register :one_way => self
|
|
58
|
+
|
|
59
|
+
# The current SyncHelper object
|
|
60
|
+
attr_accessor :sync_helper
|
|
61
|
+
|
|
62
|
+
# ID of source database (either :left or :right)
|
|
63
|
+
attr_accessor :source
|
|
64
|
+
|
|
65
|
+
# ID of target database (either :left or :right)
|
|
66
|
+
attr_accessor :target
|
|
67
|
+
|
|
68
|
+
# Array index to source row in case #sync_difference +type+ is :conflict.
|
|
69
|
+
# (As in that case the +row+ parameter is an array of left and right records.)
|
|
70
|
+
attr_accessor :source_record_index
|
|
71
|
+
|
|
72
|
+
# Provides default option for the syncer. Optional.
|
|
73
|
+
# Returns a hash with :key => value pairs.
|
|
74
|
+
def self.default_options
|
|
75
|
+
{
|
|
76
|
+
:direction => :right,
|
|
77
|
+
:delete => false, :update => true, :insert => true
|
|
78
|
+
}
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Initializes the syncer
|
|
82
|
+
# * sync_helper: The SyncHelper object provided information and utility
|
|
83
|
+
# functions.
|
|
84
|
+
def initialize(sync_helper)
|
|
85
|
+
self.sync_helper = sync_helper
|
|
86
|
+
self.source = sync_helper.sync_options[:direction] == :left ? :right : :left
|
|
87
|
+
self.target = sync_helper.sync_options[:direction] == :left ? :left : :right
|
|
88
|
+
self.source_record_index = sync_helper.sync_options[:direction] == :left ? 1 : 0
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Called to sync the provided difference.
|
|
92
|
+
# See DirectTableScan#run for a description of the +type+ and +row+ parameters.
|
|
93
|
+
def sync_difference(type, row)
|
|
94
|
+
case type
|
|
95
|
+
when source
|
|
96
|
+
if sync_helper.sync_options[:insert]
|
|
97
|
+
sync_helper.insert_record target, row
|
|
98
|
+
end
|
|
99
|
+
when target
|
|
100
|
+
if sync_helper.sync_options[:delete]
|
|
101
|
+
sync_helper.delete_record target, row
|
|
102
|
+
end
|
|
103
|
+
when :conflict
|
|
104
|
+
if sync_helper.sync_options[:update]
|
|
105
|
+
sync_helper.update_record target, row[source_record_index]
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
end
|
|
112
|
+
end
|