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.
- data/History.txt +83 -0
- data/License.txt +20 -0
- data/Manifest.txt +151 -0
- data/README.txt +37 -0
- data/bin/rubyrep +8 -0
- data/lib/rubyrep.rb +72 -0
- data/lib/rubyrep/base_runner.rb +195 -0
- data/lib/rubyrep/command_runner.rb +144 -0
- data/lib/rubyrep/committers/buffered_committer.rb +151 -0
- data/lib/rubyrep/committers/committers.rb +152 -0
- data/lib/rubyrep/configuration.rb +275 -0
- data/lib/rubyrep/connection_extenders/connection_extenders.rb +165 -0
- data/lib/rubyrep/connection_extenders/jdbc_extender.rb +65 -0
- data/lib/rubyrep/connection_extenders/mysql_extender.rb +59 -0
- data/lib/rubyrep/connection_extenders/postgresql_extender.rb +277 -0
- data/lib/rubyrep/database_proxy.rb +52 -0
- data/lib/rubyrep/direct_table_scan.rb +75 -0
- data/lib/rubyrep/generate_runner.rb +105 -0
- data/lib/rubyrep/initializer.rb +39 -0
- data/lib/rubyrep/log_helper.rb +30 -0
- data/lib/rubyrep/logged_change.rb +160 -0
- data/lib/rubyrep/logged_change_loader.rb +197 -0
- data/lib/rubyrep/noisy_connection.rb +80 -0
- data/lib/rubyrep/proxied_table_scan.rb +171 -0
- data/lib/rubyrep/proxy_block_cursor.rb +145 -0
- data/lib/rubyrep/proxy_connection.rb +431 -0
- data/lib/rubyrep/proxy_cursor.rb +44 -0
- data/lib/rubyrep/proxy_row_cursor.rb +43 -0
- data/lib/rubyrep/proxy_runner.rb +89 -0
- data/lib/rubyrep/replication_difference.rb +100 -0
- data/lib/rubyrep/replication_extenders/mysql_replication.rb +271 -0
- data/lib/rubyrep/replication_extenders/postgresql_replication.rb +236 -0
- data/lib/rubyrep/replication_extenders/replication_extenders.rb +26 -0
- data/lib/rubyrep/replication_helper.rb +142 -0
- data/lib/rubyrep/replication_initializer.rb +327 -0
- data/lib/rubyrep/replication_run.rb +142 -0
- data/lib/rubyrep/replication_runner.rb +166 -0
- data/lib/rubyrep/replicators/replicators.rb +42 -0
- data/lib/rubyrep/replicators/two_way_replicator.rb +361 -0
- data/lib/rubyrep/scan_progress_printers/progress_bar.rb +65 -0
- data/lib/rubyrep/scan_progress_printers/scan_progress_printers.rb +65 -0
- data/lib/rubyrep/scan_report_printers/scan_detail_reporter.rb +111 -0
- data/lib/rubyrep/scan_report_printers/scan_report_printers.rb +67 -0
- data/lib/rubyrep/scan_report_printers/scan_summary_reporter.rb +75 -0
- data/lib/rubyrep/scan_runner.rb +25 -0
- data/lib/rubyrep/session.rb +230 -0
- data/lib/rubyrep/sync_helper.rb +121 -0
- data/lib/rubyrep/sync_runner.rb +31 -0
- data/lib/rubyrep/syncers/syncers.rb +112 -0
- data/lib/rubyrep/syncers/two_way_syncer.rb +174 -0
- data/lib/rubyrep/table_scan.rb +54 -0
- data/lib/rubyrep/table_scan_helper.rb +46 -0
- data/lib/rubyrep/table_sorter.rb +70 -0
- data/lib/rubyrep/table_spec_resolver.rb +142 -0
- data/lib/rubyrep/table_sync.rb +90 -0
- data/lib/rubyrep/task_sweeper.rb +77 -0
- data/lib/rubyrep/trigger_mode_switcher.rb +63 -0
- data/lib/rubyrep/type_casting_cursor.rb +31 -0
- data/lib/rubyrep/uninstall_runner.rb +93 -0
- data/lib/rubyrep/version.rb +9 -0
- data/rubyrep +8 -0
- data/rubyrep.bat +4 -0
- data/setup.rb +1585 -0
- data/spec/base_runner_spec.rb +218 -0
- data/spec/buffered_committer_spec.rb +274 -0
- data/spec/command_runner_spec.rb +145 -0
- data/spec/committers_spec.rb +178 -0
- data/spec/configuration_spec.rb +203 -0
- data/spec/connection_extender_interface_spec.rb +141 -0
- data/spec/connection_extenders_registration_spec.rb +164 -0
- data/spec/database_proxy_spec.rb +48 -0
- data/spec/database_rake_spec.rb +40 -0
- data/spec/db_specific_connection_extenders_spec.rb +34 -0
- data/spec/db_specific_replication_extenders_spec.rb +38 -0
- data/spec/direct_table_scan_spec.rb +61 -0
- data/spec/dolphins.jpg +0 -0
- data/spec/generate_runner_spec.rb +84 -0
- data/spec/initializer_spec.rb +46 -0
- data/spec/log_helper_spec.rb +39 -0
- data/spec/logged_change_loader_spec.rb +68 -0
- data/spec/logged_change_spec.rb +470 -0
- data/spec/noisy_connection_spec.rb +78 -0
- data/spec/postgresql_replication_spec.rb +48 -0
- data/spec/postgresql_schema_support_spec.rb +212 -0
- data/spec/postgresql_support_spec.rb +63 -0
- data/spec/progress_bar_spec.rb +77 -0
- data/spec/proxied_table_scan_spec.rb +151 -0
- data/spec/proxy_block_cursor_spec.rb +197 -0
- data/spec/proxy_connection_spec.rb +423 -0
- data/spec/proxy_cursor_spec.rb +56 -0
- data/spec/proxy_row_cursor_spec.rb +66 -0
- data/spec/proxy_runner_spec.rb +70 -0
- data/spec/replication_difference_spec.rb +161 -0
- data/spec/replication_extender_interface_spec.rb +367 -0
- data/spec/replication_extenders_spec.rb +32 -0
- data/spec/replication_helper_spec.rb +178 -0
- data/spec/replication_initializer_spec.rb +509 -0
- data/spec/replication_run_spec.rb +443 -0
- data/spec/replication_runner_spec.rb +254 -0
- data/spec/replicators_spec.rb +36 -0
- data/spec/rubyrep_spec.rb +8 -0
- data/spec/scan_detail_reporter_spec.rb +119 -0
- data/spec/scan_progress_printers_spec.rb +68 -0
- data/spec/scan_report_printers_spec.rb +67 -0
- data/spec/scan_runner_spec.rb +50 -0
- data/spec/scan_summary_reporter_spec.rb +61 -0
- data/spec/session_spec.rb +253 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +305 -0
- data/spec/strange_name_support_spec.rb +135 -0
- data/spec/sync_helper_spec.rb +169 -0
- data/spec/sync_runner_spec.rb +78 -0
- data/spec/syncers_spec.rb +171 -0
- data/spec/table_scan_helper_spec.rb +36 -0
- data/spec/table_scan_spec.rb +49 -0
- data/spec/table_sorter_spec.rb +30 -0
- data/spec/table_spec_resolver_spec.rb +111 -0
- data/spec/table_sync_spec.rb +140 -0
- data/spec/task_sweeper_spec.rb +47 -0
- data/spec/trigger_mode_switcher_spec.rb +83 -0
- data/spec/two_way_replicator_spec.rb +721 -0
- data/spec/two_way_syncer_spec.rb +256 -0
- data/spec/type_casting_cursor_spec.rb +50 -0
- data/spec/uninstall_runner_spec.rb +93 -0
- 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
|