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,144 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
2
+
3
+ require 'optparse'
4
+
5
+ module RR
6
+
7
+ # This class implements the functionality to dispatch rubyrep commands.
8
+ class CommandRunner
9
+
10
+ # Returns a hash of all commands registered with #register.
11
+ def self.commands
12
+ @commands ||= {}
13
+ end
14
+
15
+ # Registers one or multiple commands.
16
+ # +commands+ is a hash with
17
+ # * key: name of the command
18
+ # * value: a command hash defining the command
19
+ #
20
+ # A command hash consists of
21
+ # * :+description+: short description of the command
22
+ # * :+command+: an object / class implementing the hash.
23
+ # Must have a method
24
+ #
25
+ # # runs a command
26
+ # # * +args+: array of command line parameters
27
+ # # note: will not contain the command name itself.
28
+ # def run(args)
29
+ def self.register(commands)
30
+ self.commands.merge!(commands)
31
+ end
32
+
33
+ # Prints the version to stderr
34
+ def self.show_version
35
+ $stdout.puts "rubyrep version #{RR::VERSION::STRING}"
36
+ end
37
+
38
+
39
+ # Dispatches commands as per given command line parameters.
40
+ # * +args+: array of command line parameters
41
+ def self.run(args)
42
+ status = 0
43
+ options = {}
44
+
45
+ parser = OptionParser.new do |opts|
46
+ opts.banner = <<EOS
47
+ Usage: #{$0} [general options] command [parameters, ...]
48
+
49
+ Asynchronous master-master replication of relational databases.
50
+ EOS
51
+ opts.separator ""
52
+ opts.separator "Available options:"
53
+
54
+ opts.on("--verbose", "Show errors with full stack trace") do
55
+ options[:verbose] = true
56
+ end
57
+
58
+ opts.on("-v", "--version", "Show version information.") do
59
+ show_version
60
+ options = nil
61
+ end
62
+
63
+ opts.on_tail("--help", "Show this message") do
64
+ $stderr.puts opts
65
+
66
+ $stderr.puts "\nAvailable commands:"
67
+ commands.sort.each do |command_name, command_hash|
68
+ $stderr.puts " #{command_name.ljust(15)} #{command_hash[:description]}"
69
+ end
70
+
71
+ options = nil
72
+ end
73
+ end
74
+
75
+ begin
76
+
77
+ # extract general options
78
+ general_args = []
79
+ until args.empty?
80
+ if args[0] =~ /^-/
81
+ general_args << args.shift
82
+ else
83
+ break
84
+ end
85
+ end
86
+
87
+ # parse general options
88
+ parser.parse!(general_args)
89
+
90
+ # process commands
91
+ if options # this will be +nil+ if the --help or --version are specified
92
+ if args.empty?
93
+ $stderr.puts "No command specified.\n\n"
94
+ run(['--help'])
95
+ status = 1
96
+ else
97
+ command = args[0]
98
+ if command == 'help' and args.size == 1
99
+ run(['--help'])
100
+ status = 0
101
+ elsif commands.include? command
102
+ status = commands[command][:command].run(args.slice(1, 1_000_000))
103
+ else
104
+ $stderr.puts "Error: Unknown command specified.\n\n"
105
+ run(['--help'])
106
+ status = 1
107
+ end
108
+ end
109
+ end
110
+ rescue Exception => e
111
+ $stderr.puts "Exception caught: #{e}"
112
+ $stderr.puts e.backtrace if options && options[:verbose]
113
+ status = 1
114
+ end
115
+
116
+ return status
117
+ end
118
+ end
119
+
120
+ # Command runner to show help for other commands
121
+ class HelpRunner
122
+ CommandRunner.register 'help' => {
123
+ :command => self,
124
+ :description => "Shows detailed help for the specified command"
125
+ }
126
+
127
+ # Runs the help command
128
+ # * +args+: array of command line parameters
129
+ def self.run(args)
130
+ if args[0] == 'help' or args[0] == '--help'
131
+ $stderr.puts(<<EOS)
132
+ Usage: #{$0} help [command]
133
+
134
+ Shows the help for the specified command.
135
+ EOS
136
+ 0
137
+ else
138
+ CommandRunner.run([args[0], '--help'])
139
+ end
140
+ end
141
+ end
142
+ end
143
+
144
+
@@ -0,0 +1,151 @@
1
+ module RR
2
+ module Committers
3
+
4
+ # This committer periodically commits transactions. It can be used for
5
+ # pre-replication syncs as it
6
+ # * updates the activity marker table.
7
+ # * switches existing triggers to filter out rubyrep activity
8
+ class BufferedCommitter < DefaultCommitter
9
+
10
+ # Register the committer
11
+ Committers.register :buffered_commit => self
12
+
13
+ # Unless overwritten via configuration, transactions are commited after the
14
+ # given number of record changes
15
+ DEFAULT_COMMIT_FREQUENCY = 1000
16
+
17
+ # Switches the trigger mode of the specified +table+ in the specified
18
+ # +database+ to ignore rubyrep activity.
19
+ # * +database+: identifying the database (either :+left+ or :+right+)
20
+ # * +table+: name of the table
21
+ def exclude_rr_activity(database, table)
22
+ trigger_mode_switcher.exclude_rr_activity database, table
23
+ end
24
+
25
+ # Returns the TriggerModeSwitcher (creates it if necessary)
26
+ def trigger_mode_switcher
27
+ @trigger_mode_switcher ||= TriggerModeSwitcher.new session
28
+ end
29
+
30
+ # Returns the name of the activity marker table
31
+ def activity_marker_table
32
+ @activity_marker_table ||= "#{session.configuration.options[:rep_prefix]}_running_flags"
33
+ end
34
+
35
+ # Returns +true+ if the activity marker table should be maintained.
36
+ def maintain_activity_status?
37
+ unless @activity_status_checked
38
+ @activity_status_checked = true
39
+ @maintain_activity_status = session.left.tables.include?(activity_marker_table)
40
+ end
41
+ @maintain_activity_status
42
+ end
43
+
44
+ # Returns the number of changes, after which the open transactions should
45
+ # be committed and new transactions be started.
46
+ def commit_frequency
47
+ unless @commit_frequency
48
+ @commit_frequency = session.configuration.options[:commit_frequency]
49
+ @commit_frequency ||= DEFAULT_COMMIT_FREQUENCY
50
+ end
51
+ @commit_frequency
52
+ end
53
+
54
+ # Commits the open transactions in both databases. Before committing,
55
+ # clears the rubyrep activity marker.
56
+ def commit_db_transactions
57
+ [:left, :right].each do |database|
58
+ if maintain_activity_status?
59
+ session.send(database).execute("delete from #{activity_marker_table}")
60
+ end
61
+ session.send(database).commit_db_transaction
62
+ end
63
+ end
64
+
65
+ # Begins new transactions in both databases. After starting the transaction,
66
+ # marks the activity of rubyrep.
67
+ def begin_db_transactions
68
+ [:left, :right].each do |database|
69
+ session.send(database).begin_db_transaction
70
+ if maintain_activity_status?
71
+ session.send(database).execute("insert into #{activity_marker_table} values(1)")
72
+ end
73
+ end
74
+ end
75
+
76
+ # Rolls back the open transactions in both databases.
77
+ def rollback_db_transactions
78
+ session.left.rollback_db_transaction
79
+ session.right.rollback_db_transaction
80
+ end
81
+
82
+ # Commits the open tranactions and starts new one if the #commit_frequency
83
+ # number of record changes have been executed.
84
+ def commit
85
+ @change_counter ||= 0
86
+ @change_counter += 1
87
+ if @change_counter == commit_frequency
88
+ @change_counter = 0
89
+ commit_db_transactions
90
+ begin_db_transactions
91
+ end
92
+ end
93
+
94
+ # Returns +true+ if a new transaction was started since the last
95
+ # insert / update / delete.
96
+ def new_transaction?
97
+ @change_counter == 0
98
+ end
99
+
100
+ # A new committer is created for each table sync.
101
+ # * session: a Session object representing the current database session
102
+ def initialize(session)
103
+ super
104
+ begin_db_transactions
105
+ end
106
+
107
+ # Inserts the specified record in the specified database.
108
+ # * +database+: identifying the database (either :+left+ or :+right+)
109
+ # * +table+: name of the table
110
+ # * +values+: a hash of column_name => value pairs.
111
+ def insert_record(database, table, values)
112
+ exclude_rr_activity database, table
113
+ super
114
+ commit
115
+ end
116
+
117
+ # Updates the specified record in the specified database.
118
+ # * +database+: identifying the database (either :+left+ or :+right+)
119
+ # * +table+: name of the table
120
+ # * +values+: a hash of column_name => value pairs.
121
+ # * +old_key+:
122
+ # A column_name => value hash identifying original primary key.
123
+ # If +nil+, then the primary key must be contained in +values+.
124
+ def update_record(database, table, values, old_key = nil)
125
+ exclude_rr_activity database, table
126
+ super
127
+ commit
128
+ end
129
+
130
+ # Deletes the specified record in the specified database.
131
+ # * +database+: identifying the database (either :+left+ or :+right+)
132
+ # * +table+: name of the table
133
+ # * +values+: a hash of column_name => value pairs (must only contain primary key columns).
134
+ def delete_record(database, table, values)
135
+ exclude_rr_activity database, table
136
+ super
137
+ commit
138
+ end
139
+
140
+ # Is called after the last insert / update / delete query.
141
+ # * +success+: should be true if there were no problems, false otherwise.
142
+ def finalize(success = true)
143
+ if success
144
+ commit_db_transactions
145
+ else
146
+ rollback_db_transactions
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,152 @@
1
+ module RR
2
+ # Committers are classes that implement transaction policies.
3
+ # This module provides functionality to register committers and access the
4
+ # list of registered committers.
5
+ # Every Committer needs to register itself with Committers#register.
6
+ # Each Committer must implement at the following methods:
7
+ #
8
+ # # Creates a new committer
9
+ # # * session: a Session object representing the current database session
10
+ # def initialize(session)
11
+ #
12
+ # # Inserts the specified record in the specified +database+ (either :left or :right).
13
+ # # +table+ is the name of the target table.
14
+ # # +values+ is a hash of column_name => value pairs.
15
+ # def insert_record(database, values)
16
+ #
17
+ # # Updates the specified record in the specified +database+ (either :left or :right).
18
+ # # +table+ is the name of the target table.
19
+ # # +values+ is a hash of column_name => value pairs.
20
+ # # +old_key+ is a column_name => value hash with the original primary key.
21
+ # # If +old_key+ is +nil+, then the primary key must be contained in +values+.
22
+ # def update_record(database, values, old_key)
23
+ #
24
+ # # Deletes the specified record in the specified +database+ (either :left or :right).
25
+ # # +table+ is the name of the target table.
26
+ # # +values+ is a hash of column_name => value pairs. (Only the primary key
27
+ # # values will be used and must be included in the hash.)
28
+ # def delete_record(database, values)
29
+ #
30
+ # # Is called after the last insert / update / delete query.
31
+ # def finalize
32
+ #
33
+ module Committers
34
+ # Returns a Hash of currently registered committers.
35
+ # (Empty Hash if no connection committers were defined.)
36
+ def self.committers
37
+ @committers ||= {}
38
+ @committers
39
+ end
40
+
41
+ # Registers one or multiple committers.
42
+ # committer_hash is a Hash with
43
+ # key:: The adapter symbol as used to reference the committer
44
+ # value:: The class implementing the committer
45
+ def self.register(committer_hash)
46
+ @committers ||= {}
47
+ @committers.merge! committer_hash
48
+ end
49
+
50
+ # This committer does not do anything. This means that the default DBMS
51
+ # behaviour is used (for most DBMS: every DML statement (insert, update,
52
+ # delete) runs in it's own transaction.
53
+ class DefaultCommitter
54
+
55
+ # Register the committer
56
+ Committers.register :default => self
57
+
58
+ # The current Session object
59
+ attr_accessor :session
60
+
61
+ # A hash holding the proxy connections
62
+ # E. g. {:left => <left connection>, :right => <right connection>}
63
+ attr_accessor :connections
64
+
65
+ # A new committer is created for each table sync.
66
+ # * session: a Session object representing the current database session
67
+ def initialize(session)
68
+ self.session = session
69
+ self.connections = {:left => session.left, :right => session.right}
70
+ end
71
+
72
+ # Returns +true+ if a new transaction was started since the last
73
+ # insert / update / delete.
74
+ def new_transaction?
75
+ false
76
+ end
77
+
78
+ # Inserts the specified record in the specified +database+ (either :left or :right).
79
+ # +table+ is the name of the target table.
80
+ # +values+ is a hash of column_name => value pairs.
81
+ def insert_record(database, table, values)
82
+ connections[database].insert_record(table, values)
83
+ end
84
+
85
+ # Updates the specified record in the specified +database+ (either :left or :right).
86
+ # +table+ is the name of the target table.
87
+ # +values+ is a hash of column_name => value pairs.
88
+ # # +old_key+ is a column_name => value hash with the original primary key.
89
+ # If +old_key+ is +nil+, then the primary key must be contained in +values+.
90
+ def update_record(database, table, values, old_key = nil)
91
+ connections[database].update_record(table, values, old_key)
92
+ end
93
+
94
+ # Deletes the specified record in the specified +database+ (either :left or :right).
95
+ # +table+ is the name of the target table.
96
+ # +values+ is a hash of column_name => value pairs. (Only the primary key
97
+ # values will be used and must be included in the hash.)
98
+ def delete_record(database, table, values)
99
+ connections[database].delete_record(table, values)
100
+ end
101
+
102
+ # Is called after the last insert / update / delete query.
103
+ # +success+ should be true if there were no problems, false otherwise.
104
+ def finalize(success = true)
105
+ end
106
+ end
107
+
108
+ # Starts a transaction but does never commit it.
109
+ # Useful during testing.
110
+ class NeverCommitter < DefaultCommitter
111
+ Committers.register :never_commit => self
112
+ @@current_session = nil
113
+
114
+ # Returns the last active data session
115
+ def self.current_session
116
+ @@current_session
117
+ end
118
+
119
+ # Saves the provided database session as class variable.
120
+ # Purpose: the last database session stays available after the
121
+ # NeverCommitter is destroyed so that also later the transaction rollback
122
+ # can still be executed.
123
+ def self.current_session=(session)
124
+ @@current_session = session
125
+ end
126
+
127
+ # Rolls back transactions of current session (if there is one).
128
+ # This would be called e. g. in rspec's after(:each) to ensure that
129
+ # the next test case finds the original test data.
130
+ def self.rollback_current_session
131
+ if self.current_session
132
+ self.current_session.left.rollback_db_transaction
133
+ self.current_session.right.rollback_db_transaction
134
+ self.current_session = nil
135
+ end
136
+ end
137
+
138
+ # Refer to DefaultCommitter#initialize for details.
139
+ # Starts new transactions on left and right database connectin of session.
140
+ # Additionally rolls back transactions started in previous
141
+ # +NeverCommitter+ instances.
142
+ def initialize(session)
143
+ super
144
+ self.class.rollback_current_session
145
+ self.class.current_session = session
146
+ session.left.begin_db_transaction
147
+ session.right.begin_db_transaction
148
+ end
149
+ end
150
+
151
+ end
152
+ end
@@ -0,0 +1,275 @@
1
+ module RR
2
+
3
+ # The Configuration class holds the default configuration options for Rubyrep.
4
+ # Configuration values are changed with the Initializer::run method.
5
+ class Configuration
6
+ # Connection settings for the "left" database.
7
+ # See Configuration#right for details.
8
+ attr_accessor :left
9
+
10
+ # Connection settings for the "right" database.
11
+ # Takes a similar hash as ActiveRecord::Base.establish_connection.
12
+ # Additional settings in case a proxy is used:
13
+ # * :+proxy_host+: name or IP address of where the proxy is running
14
+ # * :+proxy_port+: port on which the proxy is listening
15
+ # Other additional settings:
16
+ # * :+logger+:
17
+ # Specify an SQL statement logger for this database connection.
18
+ # Can be either
19
+ # * a logger instance itself (Logger or Log4r::Logger) or
20
+ # * the parameter to create a Logger with Logger.new
21
+ # Examples:
22
+ # config.left[:logger] = STDOUT
23
+ # config.right[:logger] = Logger.new('rubyrep_debug.log')
24
+ attr_accessor :right
25
+
26
+ # Returns true unless running on windows...
27
+ def self.true_if_running_in_a_terminal_and_not_under_windows
28
+ # Not using RUBY_PLATFORM as it should also work under JRuby
29
+ $stdout.tty? and not ENV['OS'] =~ /windows/i
30
+ end
31
+
32
+ # Default #options for a new Configuration object.
33
+ DEFAULT_OPTIONS = {
34
+ :proxy_block_size => 1000,
35
+ :row_buffer_size => 1000,
36
+ :replicator => :two_way,
37
+ :committer => :buffered_commit,
38
+ :commit_frequency => 1000,
39
+ :table_ordering => true,
40
+ :scan_progress_printer => :progress_bar,
41
+ :use_ansi => true_if_running_in_a_terminal_and_not_under_windows,
42
+ :initial_sync => true,
43
+ :adjust_sequences => true,
44
+ :sequence_adjustment_buffer => 0,
45
+ :sequence_increment => 2,
46
+ :left_sequence_offset => 0,
47
+ :right_sequence_offset => 1,
48
+ :replication_interval => 1,
49
+ :auto_key_limit => 0,
50
+ :database_connection_timeout => 5,
51
+
52
+ :rep_prefix => 'rr',
53
+ :key_sep => '|',
54
+ }
55
+
56
+ # General options.
57
+ # Possible settings:
58
+ # * :+proxy_block_size+: The proxy cursor will calculate the checksum for block_size number of records each.
59
+ # * :+row_buffer_size+:
60
+ # The number of rows that is read into memory at once.
61
+ # Only needed for database drivers that don't stream results one-by-one to the client.
62
+ # * :+committer+:
63
+ # A committer key as registered by Committers#register.
64
+ # Determines the transaction management to be used during the sync.
65
+ # * :+commit_frequency+:
66
+ # Used by BufferedCommitter. Number of changes after which the open
67
+ # transactions should be committed and new transactions be started.
68
+ # * :+table_ordering+:
69
+ # If true, sort tables before syncing as per foreign key dependencies.
70
+ # (Dependent tables are synced last to reduce risk of foreign key
71
+ # constraint violations.)
72
+ # * :+scan_progress_printer+:
73
+ # The progress printer key as registered by ScanProgressPrinters#register.
74
+ # Determines how the scan progress is visualized.
75
+ # * :+use_ansi+: Only use ANSI codes for text output if +true+.
76
+ # * :+auto_key_limit+:
77
+ # If a table has no primary keys and no primary keys have been specified
78
+ # manually using the :+primary_key_names+ option, then this option can be
79
+ # activated to simply use all columns of the table as a big combined key.
80
+ # This option specifies up to how many columns a table may have in order
81
+ # to use them as one big, combined primary key.
82
+ # Typical use case: the database has a lot of tables to map many-to-many
83
+ # relationshipts and no combined primary key is set up for them.
84
+ # Sync specific settings
85
+ # * :+before_table_sync+:
86
+ # A hook that is executed before a table sync.
87
+ # Can be either
88
+ # * a String: executed as SQL command on both databases.
89
+ # * a Proc:
90
+ # Called once before the table sync.
91
+ # The Proc is called with one parameter: the current SyncHelper instance.
92
+ # Through the sync helper there is access to the name of the synced table,
93
+ # the current session, etc
94
+ # Example:
95
+ # lambda {|helper| $stderr.puts "Hook called for #{helper.left_table}."}
96
+ # * :+after_table_sync+:
97
+ # Same as :+before_table_sync+ (but called after the sync is completed).
98
+ # * :+syncer+:
99
+ # A syncer key as registered by TableSync#register_syncer.
100
+ # Determines which sync algorithm is used.
101
+ # * further options as defined by each syncer
102
+ # Replication specific settings:
103
+ # * :+rep_prefix+: the prefix that is put in front of all created database objects
104
+ # * :+key_sep+: which string separates columns in the key column of the change log table
105
+ # * :+replicator+:
106
+ # Determines which replicator algorithm to use.
107
+ # For each replicator must also exist a corresponding +:syncer+. (It is
108
+ # used for the initial sync of a table.)
109
+ # If no +:syncer+ option is specified, than a syncer as named by this
110
+ # option is used.
111
+ # * :+initial_sync+:
112
+ # If +true+, syncs a table when initializing replication.
113
+ # Disable with care!
114
+ # (I. e. ensure that the table(s) have indeed same data in both databases
115
+ # before starting replication.)
116
+ # * :+adjust_sequences+:
117
+ # If +true+, adjust sequences to avoid number conflicts between left and
118
+ # right database during replication.
119
+ # * :+sequence_adjustement_buffer+:
120
+ # When updating a sequence, this is the additional gap to avoid sequence
121
+ # conflicts to appear due to concurrent record insertions.
122
+ # * :+sequence_increment+: new sequence value = last sequence value + this
123
+ # * :+left_sequence_offset+, +right_sequence_offset+:
124
+ # Default sequence offset for the table in the according data base.
125
+ # E. g. with a +sequence_increment+ of 2, an offset of 0 will produce even,
126
+ # an offset of 1 will produce odd numbers.
127
+ # * :+replication_interval+: time in seconds between replication runs
128
+ # * :+database_connection_timeout+:
129
+ # Time in seconds after which database connections time out.
130
+ # * :+:after_infrastructure_setup+:
131
+ # A Proc that is called after the replication infrastructure tables are
132
+ # set up. Useful to e. g. tweak the access settings for the table.
133
+ # The block is called with the current Session object.
134
+ # The block is called every time replication is started, even if the
135
+ # the infrastructure tables already existed.
136
+ #
137
+ # Example of an :+after_infrastructure_setup+ handler:
138
+ # lambda do |session|
139
+ # [:left, :right].each do |database|
140
+ # session.send(database).execute "GRANT SELECT, UPDATE, INSERT ON rr_pending_changes TO scott"
141
+ # end
142
+ # end
143
+ attr_reader :options
144
+
145
+ # Merges the specified +options+ hash into the existing options
146
+ def options=(options)
147
+ @options ||= {}
148
+ @options = @options.merge! options
149
+ end
150
+
151
+ # Array of table specifications for tables that should be processed
152
+ # Refer to #add_table_options for what constitutes a valid table specification.
153
+ def included_table_specs
154
+ @included_table_specs ||= []
155
+ end
156
+
157
+ # Array of table specifications for tables that should *not* be processed
158
+ # Refer to #add_table_options for what constitutes a valid table specification.
159
+ def excluded_table_specs
160
+ @excluded_table_specs ||= []
161
+ end
162
+
163
+ # Ensures that rubyrep infrastructure tables are excluded
164
+ def exclude_rubyrep_tables
165
+ exclude_tables Regexp.new("^#{options[:rep_prefix]}_.*")
166
+ end
167
+
168
+ # A list of tables having table specific options that should be considered
169
+ # during processing (scanned, synced, ...)
170
+ # +tables_with_options+ is a 2 element array with
171
+ # * first element: A +table_spec+ (either a table name or a regexp matching multiple tables)
172
+ # * second element: The +options+ hash (detailed format described in #add_tables
173
+ # Should only be accessed via #add_table_options and #options_for_table
174
+ def tables_with_options
175
+ @tables_with_options ||= []
176
+ end
177
+
178
+ # Adds the specified tables to the list of tables that should be processed.
179
+ # If options are provided, store them for future processing.
180
+ # Refer to #add_table_options for detailed description of parameters.
181
+ def include_tables(table_spec, options = nil)
182
+ included_table_specs << table_spec unless included_table_specs.include?(table_spec)
183
+ add_table_options(table_spec, options) if options
184
+ end
185
+ alias_method :include_table, :include_tables
186
+
187
+ # Excludes the specified table from the list of tables that should be
188
+ # processed.
189
+ # Refer to #add_table_options for detailed description of what constitutes a
190
+ # valid table specification.
191
+ def exclude_tables(table_spec)
192
+ excluded_table_specs << table_spec unless excluded_table_specs.include?(table_spec)
193
+ end
194
+ alias_method :exclude_table, :exclude_tables
195
+
196
+ # Adds the specified options for the provided +table_spec+.
197
+ # A +table_spec+ can be either
198
+ # * a table name or
199
+ # * a table pair (e. g. "my_left_table, my_right_table")
200
+ # * a regexp matching multiple tables.
201
+ # +options+ is hash with possible generic values as described under #options.
202
+ # Additional, exclusively table specific options:
203
+ # * :+primary_key_names+: array of primary key names
204
+ def add_table_options(table_spec, options)
205
+ i = nil
206
+ tables_with_options.each_with_index { |table_options, k|
207
+ i = k if table_options[0] == table_spec
208
+ }
209
+ if i
210
+ table_options = tables_with_options[i][1]
211
+ else
212
+ table_options = {}
213
+ tables_with_options << [table_spec, table_options]
214
+ end
215
+ table_options.merge! options
216
+ end
217
+ alias_method :add_table_option, :add_table_options
218
+
219
+ # Yields all table specs that have been set up with the given option
220
+ # * +key+: the option key
221
+ # Yields:
222
+ # * +table_spec+: the table specification of the matching option (or nil if non-table specific setting)
223
+ # * +option_value+: the option value for the specified +key+
224
+ def each_matching_option(key)
225
+ yield nil, options[key] if options.include?(key)
226
+ tables_with_options.each do |table_options|
227
+ yield table_options[0], table_options[1][key] if table_options[1].include? key
228
+ end
229
+ end
230
+
231
+ # Returns an option hash for the given table.
232
+ # Accumulates options for all matching table specs (most recently added options
233
+ # overwrite according options added before).
234
+ #
235
+ # Also includes the general options as returned by #options.
236
+ # (Table specific options overwrite the general options).
237
+ #
238
+ # Possible option values are described under #add_tables.
239
+ def options_for_table(table)
240
+ resulting_options = options.clone
241
+ tables_with_options.each do |table_options|
242
+ match = false
243
+ if table_options[0].kind_of? Regexp
244
+ match = (table_options[0] =~ table)
245
+ else
246
+ match = (table_options[0].sub(/(^.*),.*/,'\1').strip == table)
247
+ end
248
+ resulting_options.merge! table_options[1] if match
249
+ end
250
+
251
+ # Merge the default syncer& replicator options in
252
+ [
253
+ Syncers.configured_syncer(resulting_options),
254
+ Replicators.configured_replicator(resulting_options)
255
+ ].each do |processor_class|
256
+ if processor_class.respond_to? :default_options
257
+ default_processor_options = processor_class.default_options.clone
258
+ else
259
+ default_processor_options = {}
260
+ end
261
+ resulting_options = default_processor_options.merge!(resulting_options)
262
+ end
263
+
264
+ resulting_options
265
+ end
266
+
267
+ # initialize configuration settings
268
+ def initialize
269
+ self.left = {}
270
+ self.right = {}
271
+ self.options = DEFAULT_OPTIONS.clone
272
+ end
273
+
274
+ end
275
+ end