rubyrep 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (140) hide show
  1. data/History.txt +4 -0
  2. data/License.txt +20 -0
  3. data/Manifest.txt +137 -0
  4. data/README.txt +37 -0
  5. data/Rakefile +30 -0
  6. data/bin/rubyrep +8 -0
  7. data/config/hoe.rb +72 -0
  8. data/config/mysql_config.rb +25 -0
  9. data/config/postgres_config.rb +21 -0
  10. data/config/proxied_test_config.rb +14 -0
  11. data/config/redmine_config.rb +17 -0
  12. data/config/rep_config.rb +20 -0
  13. data/config/requirements.rb +32 -0
  14. data/config/test_config.rb +20 -0
  15. data/lib/rubyrep/base_runner.rb +195 -0
  16. data/lib/rubyrep/command_runner.rb +144 -0
  17. data/lib/rubyrep/committers/buffered_committer.rb +140 -0
  18. data/lib/rubyrep/committers/committers.rb +146 -0
  19. data/lib/rubyrep/configuration.rb +240 -0
  20. data/lib/rubyrep/connection_extenders/connection_extenders.rb +133 -0
  21. data/lib/rubyrep/connection_extenders/jdbc_extender.rb +284 -0
  22. data/lib/rubyrep/connection_extenders/mysql_extender.rb +168 -0
  23. data/lib/rubyrep/connection_extenders/postgresql_extender.rb +261 -0
  24. data/lib/rubyrep/database_proxy.rb +52 -0
  25. data/lib/rubyrep/direct_table_scan.rb +75 -0
  26. data/lib/rubyrep/generate_runner.rb +105 -0
  27. data/lib/rubyrep/initializer.rb +39 -0
  28. data/lib/rubyrep/logged_change.rb +326 -0
  29. data/lib/rubyrep/proxied_table_scan.rb +171 -0
  30. data/lib/rubyrep/proxy_block_cursor.rb +145 -0
  31. data/lib/rubyrep/proxy_connection.rb +318 -0
  32. data/lib/rubyrep/proxy_cursor.rb +44 -0
  33. data/lib/rubyrep/proxy_row_cursor.rb +43 -0
  34. data/lib/rubyrep/proxy_runner.rb +89 -0
  35. data/lib/rubyrep/replication_difference.rb +91 -0
  36. data/lib/rubyrep/replication_extenders/mysql_replication.rb +271 -0
  37. data/lib/rubyrep/replication_extenders/postgresql_replication.rb +204 -0
  38. data/lib/rubyrep/replication_extenders/replication_extenders.rb +26 -0
  39. data/lib/rubyrep/replication_helper.rb +104 -0
  40. data/lib/rubyrep/replication_initializer.rb +307 -0
  41. data/lib/rubyrep/replication_run.rb +48 -0
  42. data/lib/rubyrep/replication_runner.rb +138 -0
  43. data/lib/rubyrep/replicators/replicators.rb +37 -0
  44. data/lib/rubyrep/replicators/two_way_replicator.rb +334 -0
  45. data/lib/rubyrep/scan_progress_printers/progress_bar.rb +65 -0
  46. data/lib/rubyrep/scan_progress_printers/scan_progress_printers.rb +65 -0
  47. data/lib/rubyrep/scan_report_printers/scan_detail_reporter.rb +111 -0
  48. data/lib/rubyrep/scan_report_printers/scan_report_printers.rb +67 -0
  49. data/lib/rubyrep/scan_report_printers/scan_summary_reporter.rb +75 -0
  50. data/lib/rubyrep/scan_runner.rb +25 -0
  51. data/lib/rubyrep/session.rb +177 -0
  52. data/lib/rubyrep/sync_helper.rb +111 -0
  53. data/lib/rubyrep/sync_runner.rb +31 -0
  54. data/lib/rubyrep/syncers/syncers.rb +112 -0
  55. data/lib/rubyrep/syncers/two_way_syncer.rb +174 -0
  56. data/lib/rubyrep/table_scan.rb +54 -0
  57. data/lib/rubyrep/table_scan_helper.rb +38 -0
  58. data/lib/rubyrep/table_sorter.rb +70 -0
  59. data/lib/rubyrep/table_spec_resolver.rb +136 -0
  60. data/lib/rubyrep/table_sync.rb +68 -0
  61. data/lib/rubyrep/trigger_mode_switcher.rb +63 -0
  62. data/lib/rubyrep/type_casting_cursor.rb +31 -0
  63. data/lib/rubyrep/uninstall_runner.rb +92 -0
  64. data/lib/rubyrep/version.rb +9 -0
  65. data/lib/rubyrep.rb +68 -0
  66. data/script/destroy +14 -0
  67. data/script/generate +14 -0
  68. data/script/txt2html +74 -0
  69. data/setup.rb +1585 -0
  70. data/sims/performance/big_rep_spec.rb +100 -0
  71. data/sims/performance/big_scan_spec.rb +57 -0
  72. data/sims/performance/big_sync_spec.rb +141 -0
  73. data/sims/performance/performance.rake +228 -0
  74. data/sims/sim_helper.rb +24 -0
  75. data/spec/base_runner_spec.rb +218 -0
  76. data/spec/buffered_committer_spec.rb +271 -0
  77. data/spec/command_runner_spec.rb +145 -0
  78. data/spec/committers_spec.rb +174 -0
  79. data/spec/configuration_spec.rb +198 -0
  80. data/spec/connection_extender_interface_spec.rb +138 -0
  81. data/spec/connection_extenders_registration_spec.rb +129 -0
  82. data/spec/database_proxy_spec.rb +48 -0
  83. data/spec/database_rake_spec.rb +40 -0
  84. data/spec/db_specific_connection_extenders_spec.rb +34 -0
  85. data/spec/db_specific_replication_extenders_spec.rb +38 -0
  86. data/spec/direct_table_scan_spec.rb +61 -0
  87. data/spec/generate_runner_spec.rb +84 -0
  88. data/spec/initializer_spec.rb +46 -0
  89. data/spec/logged_change_spec.rb +480 -0
  90. data/spec/postgresql_replication_spec.rb +48 -0
  91. data/spec/postgresql_support_spec.rb +57 -0
  92. data/spec/progress_bar_spec.rb +77 -0
  93. data/spec/proxied_table_scan_spec.rb +151 -0
  94. data/spec/proxy_block_cursor_spec.rb +197 -0
  95. data/spec/proxy_connection_spec.rb +399 -0
  96. data/spec/proxy_cursor_spec.rb +56 -0
  97. data/spec/proxy_row_cursor_spec.rb +66 -0
  98. data/spec/proxy_runner_spec.rb +70 -0
  99. data/spec/replication_difference_spec.rb +160 -0
  100. data/spec/replication_extender_interface_spec.rb +365 -0
  101. data/spec/replication_extenders_spec.rb +32 -0
  102. data/spec/replication_helper_spec.rb +121 -0
  103. data/spec/replication_initializer_spec.rb +477 -0
  104. data/spec/replication_run_spec.rb +166 -0
  105. data/spec/replication_runner_spec.rb +213 -0
  106. data/spec/replicators_spec.rb +31 -0
  107. data/spec/rubyrep_spec.rb +8 -0
  108. data/spec/scan_detail_reporter_spec.rb +119 -0
  109. data/spec/scan_progress_printers_spec.rb +68 -0
  110. data/spec/scan_report_printers_spec.rb +67 -0
  111. data/spec/scan_runner_spec.rb +50 -0
  112. data/spec/scan_summary_reporter_spec.rb +61 -0
  113. data/spec/session_spec.rb +212 -0
  114. data/spec/spec.opts +1 -0
  115. data/spec/spec_helper.rb +295 -0
  116. data/spec/sync_helper_spec.rb +157 -0
  117. data/spec/sync_runner_spec.rb +78 -0
  118. data/spec/syncers_spec.rb +171 -0
  119. data/spec/table_scan_helper_spec.rb +29 -0
  120. data/spec/table_scan_spec.rb +49 -0
  121. data/spec/table_sorter_spec.rb +31 -0
  122. data/spec/table_spec_resolver_spec.rb +102 -0
  123. data/spec/table_sync_spec.rb +84 -0
  124. data/spec/trigger_mode_switcher_spec.rb +83 -0
  125. data/spec/two_way_replicator_spec.rb +551 -0
  126. data/spec/two_way_syncer_spec.rb +256 -0
  127. data/spec/type_casting_cursor_spec.rb +50 -0
  128. data/spec/uninstall_runner_spec.rb +86 -0
  129. data/tasks/database.rake +439 -0
  130. data/tasks/deployment.rake +29 -0
  131. data/tasks/environment.rake +9 -0
  132. data/tasks/java.rake +37 -0
  133. data/tasks/redmine_test.rake +47 -0
  134. data/tasks/rspec.rake +68 -0
  135. data/tasks/rubyrep.tailor +18 -0
  136. data/tasks/stats.rake +19 -0
  137. data/tasks/task_helper.rb +20 -0
  138. data.tar.gz.sig +0 -0
  139. metadata +243 -0
  140. 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