andyjeffries-rubyrep 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (125) hide show
  1. data/History.txt +83 -0
  2. data/License.txt +20 -0
  3. data/Manifest.txt +151 -0
  4. data/README.txt +37 -0
  5. data/bin/rubyrep +8 -0
  6. data/lib/rubyrep.rb +72 -0
  7. data/lib/rubyrep/base_runner.rb +195 -0
  8. data/lib/rubyrep/command_runner.rb +144 -0
  9. data/lib/rubyrep/committers/buffered_committer.rb +151 -0
  10. data/lib/rubyrep/committers/committers.rb +152 -0
  11. data/lib/rubyrep/configuration.rb +275 -0
  12. data/lib/rubyrep/connection_extenders/connection_extenders.rb +165 -0
  13. data/lib/rubyrep/connection_extenders/jdbc_extender.rb +65 -0
  14. data/lib/rubyrep/connection_extenders/mysql_extender.rb +59 -0
  15. data/lib/rubyrep/connection_extenders/postgresql_extender.rb +277 -0
  16. data/lib/rubyrep/database_proxy.rb +52 -0
  17. data/lib/rubyrep/direct_table_scan.rb +75 -0
  18. data/lib/rubyrep/generate_runner.rb +105 -0
  19. data/lib/rubyrep/initializer.rb +39 -0
  20. data/lib/rubyrep/log_helper.rb +30 -0
  21. data/lib/rubyrep/logged_change.rb +160 -0
  22. data/lib/rubyrep/logged_change_loader.rb +197 -0
  23. data/lib/rubyrep/noisy_connection.rb +80 -0
  24. data/lib/rubyrep/proxied_table_scan.rb +171 -0
  25. data/lib/rubyrep/proxy_block_cursor.rb +145 -0
  26. data/lib/rubyrep/proxy_connection.rb +431 -0
  27. data/lib/rubyrep/proxy_cursor.rb +44 -0
  28. data/lib/rubyrep/proxy_row_cursor.rb +43 -0
  29. data/lib/rubyrep/proxy_runner.rb +89 -0
  30. data/lib/rubyrep/replication_difference.rb +100 -0
  31. data/lib/rubyrep/replication_extenders/mysql_replication.rb +271 -0
  32. data/lib/rubyrep/replication_extenders/postgresql_replication.rb +236 -0
  33. data/lib/rubyrep/replication_extenders/replication_extenders.rb +26 -0
  34. data/lib/rubyrep/replication_helper.rb +142 -0
  35. data/lib/rubyrep/replication_initializer.rb +327 -0
  36. data/lib/rubyrep/replication_run.rb +142 -0
  37. data/lib/rubyrep/replication_runner.rb +166 -0
  38. data/lib/rubyrep/replicators/replicators.rb +42 -0
  39. data/lib/rubyrep/replicators/two_way_replicator.rb +361 -0
  40. data/lib/rubyrep/scan_progress_printers/progress_bar.rb +65 -0
  41. data/lib/rubyrep/scan_progress_printers/scan_progress_printers.rb +65 -0
  42. data/lib/rubyrep/scan_report_printers/scan_detail_reporter.rb +111 -0
  43. data/lib/rubyrep/scan_report_printers/scan_report_printers.rb +67 -0
  44. data/lib/rubyrep/scan_report_printers/scan_summary_reporter.rb +75 -0
  45. data/lib/rubyrep/scan_runner.rb +25 -0
  46. data/lib/rubyrep/session.rb +230 -0
  47. data/lib/rubyrep/sync_helper.rb +121 -0
  48. data/lib/rubyrep/sync_runner.rb +31 -0
  49. data/lib/rubyrep/syncers/syncers.rb +112 -0
  50. data/lib/rubyrep/syncers/two_way_syncer.rb +174 -0
  51. data/lib/rubyrep/table_scan.rb +54 -0
  52. data/lib/rubyrep/table_scan_helper.rb +46 -0
  53. data/lib/rubyrep/table_sorter.rb +70 -0
  54. data/lib/rubyrep/table_spec_resolver.rb +142 -0
  55. data/lib/rubyrep/table_sync.rb +90 -0
  56. data/lib/rubyrep/task_sweeper.rb +77 -0
  57. data/lib/rubyrep/trigger_mode_switcher.rb +63 -0
  58. data/lib/rubyrep/type_casting_cursor.rb +31 -0
  59. data/lib/rubyrep/uninstall_runner.rb +93 -0
  60. data/lib/rubyrep/version.rb +9 -0
  61. data/rubyrep +8 -0
  62. data/rubyrep.bat +4 -0
  63. data/setup.rb +1585 -0
  64. data/spec/base_runner_spec.rb +218 -0
  65. data/spec/buffered_committer_spec.rb +274 -0
  66. data/spec/command_runner_spec.rb +145 -0
  67. data/spec/committers_spec.rb +178 -0
  68. data/spec/configuration_spec.rb +203 -0
  69. data/spec/connection_extender_interface_spec.rb +141 -0
  70. data/spec/connection_extenders_registration_spec.rb +164 -0
  71. data/spec/database_proxy_spec.rb +48 -0
  72. data/spec/database_rake_spec.rb +40 -0
  73. data/spec/db_specific_connection_extenders_spec.rb +34 -0
  74. data/spec/db_specific_replication_extenders_spec.rb +38 -0
  75. data/spec/direct_table_scan_spec.rb +61 -0
  76. data/spec/dolphins.jpg +0 -0
  77. data/spec/generate_runner_spec.rb +84 -0
  78. data/spec/initializer_spec.rb +46 -0
  79. data/spec/log_helper_spec.rb +39 -0
  80. data/spec/logged_change_loader_spec.rb +68 -0
  81. data/spec/logged_change_spec.rb +470 -0
  82. data/spec/noisy_connection_spec.rb +78 -0
  83. data/spec/postgresql_replication_spec.rb +48 -0
  84. data/spec/postgresql_schema_support_spec.rb +212 -0
  85. data/spec/postgresql_support_spec.rb +63 -0
  86. data/spec/progress_bar_spec.rb +77 -0
  87. data/spec/proxied_table_scan_spec.rb +151 -0
  88. data/spec/proxy_block_cursor_spec.rb +197 -0
  89. data/spec/proxy_connection_spec.rb +423 -0
  90. data/spec/proxy_cursor_spec.rb +56 -0
  91. data/spec/proxy_row_cursor_spec.rb +66 -0
  92. data/spec/proxy_runner_spec.rb +70 -0
  93. data/spec/replication_difference_spec.rb +161 -0
  94. data/spec/replication_extender_interface_spec.rb +367 -0
  95. data/spec/replication_extenders_spec.rb +32 -0
  96. data/spec/replication_helper_spec.rb +178 -0
  97. data/spec/replication_initializer_spec.rb +509 -0
  98. data/spec/replication_run_spec.rb +443 -0
  99. data/spec/replication_runner_spec.rb +254 -0
  100. data/spec/replicators_spec.rb +36 -0
  101. data/spec/rubyrep_spec.rb +8 -0
  102. data/spec/scan_detail_reporter_spec.rb +119 -0
  103. data/spec/scan_progress_printers_spec.rb +68 -0
  104. data/spec/scan_report_printers_spec.rb +67 -0
  105. data/spec/scan_runner_spec.rb +50 -0
  106. data/spec/scan_summary_reporter_spec.rb +61 -0
  107. data/spec/session_spec.rb +253 -0
  108. data/spec/spec.opts +1 -0
  109. data/spec/spec_helper.rb +305 -0
  110. data/spec/strange_name_support_spec.rb +135 -0
  111. data/spec/sync_helper_spec.rb +169 -0
  112. data/spec/sync_runner_spec.rb +78 -0
  113. data/spec/syncers_spec.rb +171 -0
  114. data/spec/table_scan_helper_spec.rb +36 -0
  115. data/spec/table_scan_spec.rb +49 -0
  116. data/spec/table_sorter_spec.rb +30 -0
  117. data/spec/table_spec_resolver_spec.rb +111 -0
  118. data/spec/table_sync_spec.rb +140 -0
  119. data/spec/task_sweeper_spec.rb +47 -0
  120. data/spec/trigger_mode_switcher_spec.rb +83 -0
  121. data/spec/two_way_replicator_spec.rb +721 -0
  122. data/spec/two_way_syncer_spec.rb +256 -0
  123. data/spec/type_casting_cursor_spec.rb +50 -0
  124. data/spec/uninstall_runner_spec.rb +93 -0
  125. metadata +190 -0
@@ -0,0 +1,121 @@
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
+ include LogHelper
9
+
10
+ # The current +TableSync+ instance
11
+ attr_accessor :table_sync
12
+
13
+ # The active +Session+
14
+ def session; table_sync.session; end
15
+
16
+ # Name of the left table
17
+ def left_table; table_sync.left_table; end
18
+
19
+ # Name of the right table
20
+ def right_table; table_sync.right_table; end
21
+
22
+ # A hash with
23
+ # :+left+: name of the table in the left database
24
+ # :+right+: name of the table in the right database
25
+ def tables
26
+ @tables ||= {:left => left_table, :right => right_table}
27
+ end
28
+
29
+ # Given a column_name => value hash of a full row, returns a
30
+ # column_name => value hash of the primary key columns.
31
+ # * +row+: the full row
32
+ # Returns
33
+ def extract_key(row)
34
+ row.reject {|column, value| not primary_key_names.include? column }
35
+ end
36
+
37
+ # Sync options for the current table sync
38
+ def sync_options; @sync_options ||= table_sync.sync_options; end
39
+
40
+ # Delegates to Committers::BufferedCommitter#insert_record
41
+ def insert_record(database, table, values)
42
+ committer.insert_record(database, tables[database], values)
43
+ end
44
+
45
+ # Delegates to Committers::BufferedCommitter#update_record
46
+ def update_record(database, table, values, old_key = nil)
47
+ committer.update_record(database, tables[database], values, old_key)
48
+ end
49
+
50
+ # Delegates to Committers::BufferedCommitter#delete_record
51
+ def delete_record(database, table, values)
52
+ committer.delete_record(database, tables[database], values)
53
+ end
54
+
55
+ # Return the committer, creating it if not yet there.
56
+ def committer
57
+ unless @committer
58
+ committer_class = Committers::committers[sync_options[:committer]]
59
+ @committer = committer_class.new(session)
60
+ end
61
+ @committer
62
+ end
63
+ private :committer
64
+
65
+ # Checks if the event log table already exists and creates it if necessary
66
+ def ensure_event_log
67
+ unless @ensured_event_log
68
+ ReplicationInitializer.new(session).ensure_event_log
69
+ @ensured_event_log = true
70
+ end
71
+ end
72
+
73
+ # Returns an array of primary key names for the synced table
74
+ def primary_key_names
75
+ @primary_key_names ||= session.left.primary_key_names(left_table)
76
+ end
77
+ private :primary_key_names
78
+
79
+ # Logs the outcome of a replication into the replication log table.
80
+ # * +row+: a column_name => value hash for at least the primary keys of the record
81
+ # * +type+: string describing the type of the sync
82
+ # * +outcome+: string describing what's done about the sync
83
+ # * +details+: string with further details regarding the sync
84
+ def log_sync_outcome(row, type, outcome, details = nil)
85
+ ensure_event_log
86
+ if primary_key_names.size == 1
87
+ key = row[primary_key_names[0]]
88
+ else
89
+ key_parts = primary_key_names.map do |column_name|
90
+ %Q("#{column_name}"=>#{row[column_name].to_s.inspect})
91
+ end
92
+ key = key_parts.join(', ')
93
+ end
94
+ sync_outcome, sync_details = fit_description_columns(outcome, details)
95
+
96
+ session.left.insert_record "#{sync_options[:rep_prefix]}_logged_events", {
97
+ :activity => 'sync',
98
+ :change_table => left_table,
99
+ :diff_type => type.to_s,
100
+ :change_key => key,
101
+ :left_change_type => nil,
102
+ :right_change_type => nil,
103
+ :description => sync_outcome,
104
+ :long_description => sync_details,
105
+ :event_time => Time.now,
106
+ :diff_dump => nil
107
+ }
108
+ end
109
+
110
+ # Asks the committer (if it exists) to finalize any open transactions
111
+ # +success+ should be true if there were no problems, false otherwise.
112
+ def finalize(success = true)
113
+ @committer.finalize(success) if @committer
114
+ end
115
+
116
+ # Creates a new SyncHelper for the given +TableSync+ instance.
117
+ def initialize(table_sync)
118
+ self.table_sync = table_sync
119
+ end
120
+ end
121
+ 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, sync_helper.tables[target], row
98
+ end
99
+ when target
100
+ if sync_helper.sync_options[:delete]
101
+ sync_helper.delete_record target, sync_helper.tables[target], row
102
+ end
103
+ when :conflict
104
+ if sync_helper.sync_options[:update]
105
+ sync_helper.update_record target, sync_helper.tables[target], row[source_record_index]
106
+ end
107
+ end
108
+ end
109
+ end
110
+
111
+ end
112
+ end
@@ -0,0 +1,174 @@
1
+ module RR
2
+ module Syncers
3
+ # This syncer implements a two way sync.
4
+ # Syncer options relevant for this syncer:
5
+ # * :+left_record_handling+, :+right_record_handling+:
6
+ # Handling of records only existing only in the named database.
7
+ # Can be any of the following:
8
+ # * :+ignore+: No action.
9
+ # * :+delete+: Delete from the source database.
10
+ # * :+insert+: Insert in the target database. *Default* *Setting*
11
+ # * +Proc+ object:
12
+ # If a Proc object is given, it is responsible for dealing with the
13
+ # record. Called with the following parameters:
14
+ # * sync_helper: The current SyncHelper instance.
15
+ # * type: :+left+ or :+right+ to designate source database
16
+ # * row: column_name => value hash representing the row
17
+ # * :+sync_conflict_handling+:
18
+ # Handling of conflicting records. Can be any of the following:
19
+ # * :+ignore+: No action. *Default* *Setting*
20
+ # * :+left_wins+: Update right database with the field values in the left db.
21
+ # * :+right_wins+: Update left database with the field values in the right db.
22
+ # * +Proc+ object:
23
+ # If a Proc object is given, it is responsible for dealing with the
24
+ # record. Called with the following parameters:
25
+ # * sync_helper: The current SyncHelper instance.
26
+ # * type: always :+conflict+
27
+ # * rows: A two element array of rows (column_name => value hashes).
28
+ # First left, than right record.
29
+ # * :+logged_sync_events+:
30
+ # Specifies which types of syncs are logged.
31
+ # Is either a single value or an array of multiple ones.
32
+ # Default: [:ignored_conflicts]
33
+ # Possible values:
34
+ # * :+ignored_changes+: log ignored (but not synced) non-conflict changes
35
+ # * :+all_changes+: log all non-conflict changes
36
+ # * :+ignored_conflicts+: log ignored (but not synced) conflicts
37
+ # * :+all_conflicts+: log all conflicts
38
+ #
39
+ # Example of using a Proc object:
40
+ # lambda do |sync_helper, type, row|
41
+ # # delete records existing only in the left database.
42
+ # sync_helper.delete(type, row) if type == :left
43
+ # end
44
+ class TwoWaySyncer
45
+
46
+ # Register the syncer
47
+ Syncers.register :two_way => self
48
+
49
+ # The current SyncHelper object
50
+ attr_accessor :sync_helper
51
+
52
+ # Provides default option for the syncer. Optional.
53
+ # Returns a hash with key => value pairs.
54
+ def self.default_options
55
+ {
56
+ :left_record_handling => :insert,
57
+ :right_record_handling => :insert,
58
+ :sync_conflict_handling => :ignore,
59
+ :logged_sync_events => [:ignored_conflicts]
60
+ }
61
+ end
62
+
63
+ # Verifies if the given :+left_record_handling+ / :+right_record_handling+
64
+ # option is valid.
65
+ # Raises an ArgumentError if option is invalid
66
+ def validate_left_right_record_handling_option(option)
67
+ unless option.respond_to? :call
68
+ unless [:ignore, :delete, :insert].include? option
69
+ raise ArgumentError.new("#{option.inspect} not a valid :left_record_handling / :right_record_handling option")
70
+ end
71
+ end
72
+ end
73
+
74
+ # Verifies if the given :+sync_conflict_handling+ option is valid.
75
+ # Raises an ArgumentError if option is invalid
76
+ def validate_conflict_handling_option(option)
77
+ unless option.respond_to? :call
78
+ unless [:ignore, :right_wins, :left_wins].include? option
79
+ raise ArgumentError.new("#{option.inspect} not a valid :sync_conflict_handling option")
80
+ end
81
+ end
82
+ end
83
+
84
+ # Verifies if the given :+replication_logging+ option /options is / are valid.
85
+ # Raises an ArgumentError if invalid
86
+ def validate_logging_options(options)
87
+ values = [options].flatten # ensure that I have an array
88
+ values.each do |value|
89
+ unless [:ignored_changes, :all_changes, :ignored_conflicts, :all_conflicts].include? value
90
+ raise ArgumentError.new("#{value.inspect} not a valid :logged_sync_events option")
91
+ end
92
+ end
93
+ end
94
+
95
+ # Initializes the syncer
96
+ # * sync_helper:
97
+ # The SyncHelper object provided information and utility functions.
98
+ # Raises an ArgumentError if any of the option in sync_helper.sync_options
99
+ # is invalid.
100
+ def initialize(sync_helper)
101
+ validate_left_right_record_handling_option sync_helper.sync_options[:left_record_handling]
102
+ validate_left_right_record_handling_option sync_helper.sync_options[:right_record_handling]
103
+ validate_conflict_handling_option sync_helper.sync_options[:sync_conflict_handling]
104
+ validate_logging_options sync_helper.sync_options[:logged_sync_events]
105
+
106
+ self.sync_helper = sync_helper
107
+ end
108
+
109
+ # Sync type descriptions that are written into the event log
110
+ TYPE_DESCRIPTIONS = {
111
+ :left => 'left_record',
112
+ :right => 'right_record',
113
+ :conflict => 'conflict'
114
+ }
115
+
116
+ # Returns the :logged_sync_events option values.
117
+ def log_option_values
118
+ @log_option_values ||= [sync_helper.sync_options[:logged_sync_events]].flatten
119
+ end
120
+ private :log_option_values
121
+
122
+ # Logs a sync event into the event log table as per configuration options.
123
+ # * +type+: Refer to DirectTableScan#run for a description
124
+ # * +action+: the sync action that is executed
125
+ # (The :+left_record_handling+ / :+right_record_handling+ or
126
+ # :+sync_conflict_handling+ option)
127
+ # * +row+: Refer to DirectTableScan#run for a description
128
+ def log_sync_outcome(type, action, row)
129
+ if type == :conflict
130
+ return unless log_option_values.include?(:all_conflicts) or log_option_values.include?(:ignored_conflicts)
131
+ return if action != :ignore and not log_option_values.include?(:all_conflicts)
132
+ row = row[0] # Extract left row from row array
133
+ else
134
+ return unless log_option_values.include?(:all_changes) or log_option_values.include?(:ignored_changes)
135
+ return if action != :ignore and not log_option_values.include?(:all_changes)
136
+ end
137
+
138
+ sync_helper.log_sync_outcome row, TYPE_DESCRIPTIONS[type], action
139
+ end
140
+
141
+ # Called to sync the provided difference.
142
+ # See DirectTableScan#run for a description of the +type+ and +row+ parameters.
143
+ def sync_difference(type, row)
144
+ if type == :left or type == :right
145
+ option_key = type == :left ? :left_record_handling : :right_record_handling
146
+ option = sync_helper.sync_options[option_key]
147
+ log_sync_outcome type, option, row unless option.respond_to?(:call)
148
+ if option == :ignore
149
+ # nothing to do
150
+ elsif option == :delete
151
+ sync_helper.delete_record type, sync_helper.tables[type], row
152
+ elsif option == :insert
153
+ target = (type == :left ? :right : :left)
154
+ sync_helper.insert_record target, sync_helper.tables[target], row
155
+ else #option must be a Proc
156
+ option.call sync_helper, type, row
157
+ end
158
+ else
159
+ option = sync_helper.sync_options[:sync_conflict_handling]
160
+ log_sync_outcome type, option, row unless option.respond_to?(:call)
161
+ if option == :ignore
162
+ # nothing to do
163
+ elsif option == :right_wins
164
+ sync_helper.update_record :left, sync_helper.tables[:left], row[1]
165
+ elsif option == :left_wins
166
+ sync_helper.update_record :right, sync_helper.tables[:right], row[0]
167
+ else #option must be a Proc
168
+ option.call sync_helper, type, row
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,54 @@
1
+ module RR
2
+
3
+ # Shared functionality for DirectTableScan and ProxiedTableScan
4
+ class TableScan
5
+ include TableScanHelper
6
+
7
+ # The current Session object
8
+ attr_accessor :session
9
+
10
+ # Name of the left table
11
+ attr_accessor :left_table
12
+
13
+ # Name of the right table
14
+ attr_accessor :right_table
15
+
16
+ # Cached array of primary key names
17
+ attr_accessor :primary_key_names
18
+
19
+ # Receives the active ScanProgressPrinters class
20
+ attr_accessor :progress_printer
21
+
22
+ # Returns a hash of scan options for this table scan.
23
+ def scan_options
24
+ @scan_options ||= session.configuration.options_for_table(left_table)
25
+ end
26
+
27
+ # Inform new progress to progress printer
28
+ # +steps+ is the number of processed records.
29
+ def update_progress(steps)
30
+ return unless progress_printer
31
+ unless @progress_printer_instance
32
+ total_records =
33
+ session.left.select_one("select count(*) as n from #{session.left.quote_table_name(left_table)}")['n'].to_i +
34
+ session.right.select_one("select count(*) as n from #{session.right.quote_table_name(right_table)}")['n'].to_i
35
+ @progress_printer_instance = progress_printer.new(total_records, session, left_table, right_table)
36
+ end
37
+ @progress_printer_instance.step(steps)
38
+ end
39
+
40
+ # Creates a new DirectTableScan instance
41
+ # * session: a Session object representing the current database session
42
+ # * left_table: name of the table in the left database
43
+ # * right_table: name of the table in the right database. If not given, same like left_table
44
+ def initialize(session, left_table, right_table = nil)
45
+ if session.left.primary_key_names(left_table).empty?
46
+ raise "Table '#{left_table}' doesn't have a primary key. Cannot scan."
47
+ end
48
+
49
+ self.session, self.left_table, self.right_table = session, left_table, right_table
50
+ self.right_table ||= self.left_table
51
+ self.primary_key_names = session.left.primary_key_names left_table
52
+ end
53
+ end
54
+ end