andyjeffries-rubyrep 1.2.1

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