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