rubyrep 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. data/History.txt +4 -0
  2. data/License.txt +20 -0
  3. data/Manifest.txt +137 -0
  4. data/README.txt +37 -0
  5. data/Rakefile +30 -0
  6. data/bin/rubyrep +8 -0
  7. data/config/hoe.rb +72 -0
  8. data/config/mysql_config.rb +25 -0
  9. data/config/postgres_config.rb +21 -0
  10. data/config/proxied_test_config.rb +14 -0
  11. data/config/redmine_config.rb +17 -0
  12. data/config/rep_config.rb +20 -0
  13. data/config/requirements.rb +32 -0
  14. data/config/test_config.rb +20 -0
  15. data/lib/rubyrep/base_runner.rb +195 -0
  16. data/lib/rubyrep/command_runner.rb +144 -0
  17. data/lib/rubyrep/committers/buffered_committer.rb +140 -0
  18. data/lib/rubyrep/committers/committers.rb +146 -0
  19. data/lib/rubyrep/configuration.rb +240 -0
  20. data/lib/rubyrep/connection_extenders/connection_extenders.rb +133 -0
  21. data/lib/rubyrep/connection_extenders/jdbc_extender.rb +284 -0
  22. data/lib/rubyrep/connection_extenders/mysql_extender.rb +168 -0
  23. data/lib/rubyrep/connection_extenders/postgresql_extender.rb +261 -0
  24. data/lib/rubyrep/database_proxy.rb +52 -0
  25. data/lib/rubyrep/direct_table_scan.rb +75 -0
  26. data/lib/rubyrep/generate_runner.rb +105 -0
  27. data/lib/rubyrep/initializer.rb +39 -0
  28. data/lib/rubyrep/logged_change.rb +326 -0
  29. data/lib/rubyrep/proxied_table_scan.rb +171 -0
  30. data/lib/rubyrep/proxy_block_cursor.rb +145 -0
  31. data/lib/rubyrep/proxy_connection.rb +318 -0
  32. data/lib/rubyrep/proxy_cursor.rb +44 -0
  33. data/lib/rubyrep/proxy_row_cursor.rb +43 -0
  34. data/lib/rubyrep/proxy_runner.rb +89 -0
  35. data/lib/rubyrep/replication_difference.rb +91 -0
  36. data/lib/rubyrep/replication_extenders/mysql_replication.rb +271 -0
  37. data/lib/rubyrep/replication_extenders/postgresql_replication.rb +204 -0
  38. data/lib/rubyrep/replication_extenders/replication_extenders.rb +26 -0
  39. data/lib/rubyrep/replication_helper.rb +104 -0
  40. data/lib/rubyrep/replication_initializer.rb +307 -0
  41. data/lib/rubyrep/replication_run.rb +48 -0
  42. data/lib/rubyrep/replication_runner.rb +138 -0
  43. data/lib/rubyrep/replicators/replicators.rb +37 -0
  44. data/lib/rubyrep/replicators/two_way_replicator.rb +334 -0
  45. data/lib/rubyrep/scan_progress_printers/progress_bar.rb +65 -0
  46. data/lib/rubyrep/scan_progress_printers/scan_progress_printers.rb +65 -0
  47. data/lib/rubyrep/scan_report_printers/scan_detail_reporter.rb +111 -0
  48. data/lib/rubyrep/scan_report_printers/scan_report_printers.rb +67 -0
  49. data/lib/rubyrep/scan_report_printers/scan_summary_reporter.rb +75 -0
  50. data/lib/rubyrep/scan_runner.rb +25 -0
  51. data/lib/rubyrep/session.rb +177 -0
  52. data/lib/rubyrep/sync_helper.rb +111 -0
  53. data/lib/rubyrep/sync_runner.rb +31 -0
  54. data/lib/rubyrep/syncers/syncers.rb +112 -0
  55. data/lib/rubyrep/syncers/two_way_syncer.rb +174 -0
  56. data/lib/rubyrep/table_scan.rb +54 -0
  57. data/lib/rubyrep/table_scan_helper.rb +38 -0
  58. data/lib/rubyrep/table_sorter.rb +70 -0
  59. data/lib/rubyrep/table_spec_resolver.rb +136 -0
  60. data/lib/rubyrep/table_sync.rb +68 -0
  61. data/lib/rubyrep/trigger_mode_switcher.rb +63 -0
  62. data/lib/rubyrep/type_casting_cursor.rb +31 -0
  63. data/lib/rubyrep/uninstall_runner.rb +92 -0
  64. data/lib/rubyrep/version.rb +9 -0
  65. data/lib/rubyrep.rb +68 -0
  66. data/script/destroy +14 -0
  67. data/script/generate +14 -0
  68. data/script/txt2html +74 -0
  69. data/setup.rb +1585 -0
  70. data/sims/performance/big_rep_spec.rb +100 -0
  71. data/sims/performance/big_scan_spec.rb +57 -0
  72. data/sims/performance/big_sync_spec.rb +141 -0
  73. data/sims/performance/performance.rake +228 -0
  74. data/sims/sim_helper.rb +24 -0
  75. data/spec/base_runner_spec.rb +218 -0
  76. data/spec/buffered_committer_spec.rb +271 -0
  77. data/spec/command_runner_spec.rb +145 -0
  78. data/spec/committers_spec.rb +174 -0
  79. data/spec/configuration_spec.rb +198 -0
  80. data/spec/connection_extender_interface_spec.rb +138 -0
  81. data/spec/connection_extenders_registration_spec.rb +129 -0
  82. data/spec/database_proxy_spec.rb +48 -0
  83. data/spec/database_rake_spec.rb +40 -0
  84. data/spec/db_specific_connection_extenders_spec.rb +34 -0
  85. data/spec/db_specific_replication_extenders_spec.rb +38 -0
  86. data/spec/direct_table_scan_spec.rb +61 -0
  87. data/spec/generate_runner_spec.rb +84 -0
  88. data/spec/initializer_spec.rb +46 -0
  89. data/spec/logged_change_spec.rb +480 -0
  90. data/spec/postgresql_replication_spec.rb +48 -0
  91. data/spec/postgresql_support_spec.rb +57 -0
  92. data/spec/progress_bar_spec.rb +77 -0
  93. data/spec/proxied_table_scan_spec.rb +151 -0
  94. data/spec/proxy_block_cursor_spec.rb +197 -0
  95. data/spec/proxy_connection_spec.rb +399 -0
  96. data/spec/proxy_cursor_spec.rb +56 -0
  97. data/spec/proxy_row_cursor_spec.rb +66 -0
  98. data/spec/proxy_runner_spec.rb +70 -0
  99. data/spec/replication_difference_spec.rb +160 -0
  100. data/spec/replication_extender_interface_spec.rb +365 -0
  101. data/spec/replication_extenders_spec.rb +32 -0
  102. data/spec/replication_helper_spec.rb +121 -0
  103. data/spec/replication_initializer_spec.rb +477 -0
  104. data/spec/replication_run_spec.rb +166 -0
  105. data/spec/replication_runner_spec.rb +213 -0
  106. data/spec/replicators_spec.rb +31 -0
  107. data/spec/rubyrep_spec.rb +8 -0
  108. data/spec/scan_detail_reporter_spec.rb +119 -0
  109. data/spec/scan_progress_printers_spec.rb +68 -0
  110. data/spec/scan_report_printers_spec.rb +67 -0
  111. data/spec/scan_runner_spec.rb +50 -0
  112. data/spec/scan_summary_reporter_spec.rb +61 -0
  113. data/spec/session_spec.rb +212 -0
  114. data/spec/spec.opts +1 -0
  115. data/spec/spec_helper.rb +295 -0
  116. data/spec/sync_helper_spec.rb +157 -0
  117. data/spec/sync_runner_spec.rb +78 -0
  118. data/spec/syncers_spec.rb +171 -0
  119. data/spec/table_scan_helper_spec.rb +29 -0
  120. data/spec/table_scan_spec.rb +49 -0
  121. data/spec/table_sorter_spec.rb +31 -0
  122. data/spec/table_spec_resolver_spec.rb +102 -0
  123. data/spec/table_sync_spec.rb +84 -0
  124. data/spec/trigger_mode_switcher_spec.rb +83 -0
  125. data/spec/two_way_replicator_spec.rb +551 -0
  126. data/spec/two_way_syncer_spec.rb +256 -0
  127. data/spec/type_casting_cursor_spec.rb +50 -0
  128. data/spec/uninstall_runner_spec.rb +86 -0
  129. data/tasks/database.rake +439 -0
  130. data/tasks/deployment.rake +29 -0
  131. data/tasks/environment.rake +9 -0
  132. data/tasks/java.rake +37 -0
  133. data/tasks/redmine_test.rake +47 -0
  134. data/tasks/rspec.rake +68 -0
  135. data/tasks/rubyrep.tailor +18 -0
  136. data/tasks/stats.rake +19 -0
  137. data/tasks/task_helper.rb +20 -0
  138. data.tar.gz.sig +0 -0
  139. metadata +243 -0
  140. metadata.gz.sig +0 -0
@@ -0,0 +1,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,140 @@
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
+ def exclude_rr_activity(database, table)
20
+ trigger_mode_switcher.exclude_rr_activity database, table
21
+ end
22
+
23
+ # Returns the trigger mode switcher (creates it if necessary)
24
+ def trigger_mode_switcher
25
+ @trigger_mode_switcher ||= TriggerModeSwitcher.new session
26
+ end
27
+
28
+ # Returns the name of the activity marker table
29
+ def activity_marker_table
30
+ @activity_marker_table ||= "#{session.configuration.options[:rep_prefix]}_running_flags"
31
+ end
32
+
33
+ # Returns +true+ if the activity marker table should be maintained.
34
+ def maintain_activity_status?
35
+ unless @activity_status_checked
36
+ @activity_status_checked = true
37
+ @maintain_activity_status = session.left.tables.include?(activity_marker_table)
38
+ end
39
+ @maintain_activity_status
40
+ end
41
+
42
+ # Returns the number of changes, after which the open transactions should
43
+ # be committed and new transactions be started.
44
+ def commit_frequency
45
+ unless @commit_frequency
46
+ @commit_frequency = session.configuration.options[:commit_frequency]
47
+ @commit_frequency ||= DEFAULT_COMMIT_FREQUENCY
48
+ end
49
+ @commit_frequency
50
+ end
51
+
52
+ # Commits the open transactions in both databases. Before committing,
53
+ # clears the rubyrep activity marker.
54
+ def commit_db_transactions
55
+ [:left, :right].each do |database|
56
+ if maintain_activity_status?
57
+ session.send(database).execute("delete from #{activity_marker_table}")
58
+ end
59
+ session.send(database).commit_db_transaction
60
+ end
61
+ end
62
+
63
+ # Begins new transactions in both databases. After starting the transaction,
64
+ # marks the activity of rubyrep.
65
+ def begin_db_transactions
66
+ [:left, :right].each do |database|
67
+ session.send(database).begin_db_transaction
68
+ if maintain_activity_status?
69
+ session.send(database).execute("insert into #{activity_marker_table} values(1)")
70
+ end
71
+ end
72
+ end
73
+
74
+ # Rolls back the open transactions in both databases.
75
+ def rollback_db_transactions
76
+ session.left.rollback_db_transaction
77
+ session.right.rollback_db_transaction
78
+ end
79
+
80
+ # Commits the open tranactions and starts new one if the #commit_frequency
81
+ # number of record changes have been executed.
82
+ def commit
83
+ @change_counter ||= 0
84
+ @change_counter += 1
85
+ if @change_counter == commit_frequency
86
+ @change_counter = 0
87
+ commit_db_transactions
88
+ begin_db_transactions
89
+ end
90
+ end
91
+
92
+ # A new committer is created for each table sync.
93
+ # * session: a Session object representing the current database session
94
+ def initialize(session)
95
+ super
96
+ begin_db_transactions
97
+ end
98
+
99
+ # Inserts the specified record in the specified +database+ (either :left or :right).
100
+ # +table+ is the name of the target table.
101
+ # +values+ is a hash of column_name => value pairs.
102
+ def insert_record(database, table, values)
103
+ exclude_rr_activity database, table
104
+ super
105
+ commit
106
+ end
107
+
108
+ # Updates the specified record in the specified +database+ (either :left or :right).
109
+ # +table+ is the name of the target table.
110
+ # +values+ is a hash of column_name => value pairs.
111
+ # +old_key+ is a column_name => value hash with the original primary key.
112
+ # If +old_key+ is +nil+, then the primary key must be contained in +values+.
113
+ def update_record(database, table, values, old_key = nil)
114
+ exclude_rr_activity database, table
115
+ super
116
+ commit
117
+ end
118
+
119
+ # Deletes the specified record in the specified +database+ (either :left or :right).
120
+ # +table+ is the name of the target table.
121
+ # +values+ is a hash of column_name => value pairs. (Only the primary key
122
+ # values will be used and must be included in the hash.)
123
+ def delete_record(database, table, values)
124
+ exclude_rr_activity database, table
125
+ super
126
+ commit
127
+ end
128
+
129
+ # Is called after the last insert / update / delete query.
130
+ # +success+ should be true if there were no problems, false otherwise.
131
+ def finalize(success = true)
132
+ if success
133
+ commit_db_transactions
134
+ else
135
+ rollback_db_transactions
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,146 @@
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
+ # Inserts the specified record in the specified +database+ (either :left or :right).
73
+ # +table+ is the name of the target table.
74
+ # +values+ is a hash of column_name => value pairs.
75
+ def insert_record(database, table, values)
76
+ connections[database].insert_record(table, values)
77
+ end
78
+
79
+ # Updates the specified record in the specified +database+ (either :left or :right).
80
+ # +table+ is the name of the target table.
81
+ # +values+ is a hash of column_name => value pairs.
82
+ # # +old_key+ is a column_name => value hash with the original primary key.
83
+ # If +old_key+ is +nil+, then the primary key must be contained in +values+.
84
+ def update_record(database, table, values, old_key = nil)
85
+ connections[database].update_record(table, values, old_key)
86
+ end
87
+
88
+ # Deletes the specified record in the specified +database+ (either :left or :right).
89
+ # +table+ is the name of the target table.
90
+ # +values+ is a hash of column_name => value pairs. (Only the primary key
91
+ # values will be used and must be included in the hash.)
92
+ def delete_record(database, table, values)
93
+ connections[database].delete_record(table, values)
94
+ end
95
+
96
+ # Is called after the last insert / update / delete query.
97
+ # +success+ should be true if there were no problems, false otherwise.
98
+ def finalize(success = true)
99
+ end
100
+ end
101
+
102
+ # Starts a transaction but does never commit it.
103
+ # Useful during testing.
104
+ class NeverCommitter < DefaultCommitter
105
+ Committers.register :never_commit => self
106
+ @@current_session = nil
107
+
108
+ # Returns the last active data session
109
+ def self.current_session
110
+ @@current_session
111
+ end
112
+
113
+ # Saves the provided database session as class variable.
114
+ # Purpose: the last database session stays available after the
115
+ # NeverCommitter is destroyed so that also later the transaction rollback
116
+ # can still be executed.
117
+ def self.current_session=(session)
118
+ @@current_session = session
119
+ end
120
+
121
+ # Rolls back transactions of current session (if there is one).
122
+ # This would be called e. g. in rspec's after(:each) to ensure that
123
+ # the next test case finds the original test data.
124
+ def self.rollback_current_session
125
+ if self.current_session
126
+ self.current_session.left.rollback_db_transaction
127
+ self.current_session.right.rollback_db_transaction
128
+ self.current_session = nil
129
+ end
130
+ end
131
+
132
+ # Refer to DefaultCommitter#initialize for details.
133
+ # Starts new transactions on left and right database connectin of session.
134
+ # Additionally rolls back transactions started in previous
135
+ # +NeverCommitter+ instances.
136
+ def initialize(session)
137
+ super
138
+ self.class.rollback_current_session
139
+ self.class.current_session = session
140
+ session.left.begin_db_transaction
141
+ session.right.begin_db_transaction
142
+ end
143
+ end
144
+
145
+ end
146
+ end
@@ -0,0 +1,240 @@
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
+ attr_accessor :right
16
+
17
+ # Returns true unless running on windows...
18
+ def self.true_if_running_in_a_terminal_and_not_under_windows
19
+ # Not using RUBY_PLATFORM as it should also work under JRuby
20
+ $stdout.tty? and not ENV['OS'] =~ /windows/i
21
+ end
22
+
23
+ # Default #options for a new Configuration object.
24
+ DEFAULT_OPTIONS = {
25
+ :proxy_block_size => 1000,
26
+ :row_buffer_size => 1000,
27
+ :replicator => :two_way,
28
+ :committer => :buffered_commit,
29
+ :commit_frequency => 1000,
30
+ :table_ordering => true,
31
+ :scan_progress_printer => :progress_bar,
32
+ :use_ansi => true_if_running_in_a_terminal_and_not_under_windows,
33
+ :adjust_sequences => true,
34
+ :sequence_adjustment_buffer => 0,
35
+ :sequence_increment => 2,
36
+ :left_sequence_offset => 0,
37
+ :right_sequence_offset => 1,
38
+ :replication_interval => 1,
39
+ :auto_key_limit => 0,
40
+
41
+ :rep_prefix => 'rr',
42
+ :key_sep => '|',
43
+ }
44
+
45
+ # General options.
46
+ # Possible settings:
47
+ # * :+proxy_block_size+: The proxy cursor will calculate the checksum for block_size number of records each.
48
+ # * :+row_buffer_size+:
49
+ # The number of rows that is read into memory at once.
50
+ # Only needed for database drivers that don't stream results one-by-one to the client.
51
+ # * :+committer+:
52
+ # A committer key as registered by Committers#register.
53
+ # Determines the transaction management to be used during the sync.
54
+ # * :+commit_frequency+:
55
+ # Used by BufferedCommitter. Number of changes after which the open
56
+ # transactions should be committed and new transactions be started.
57
+ # * :+table_ordering+:
58
+ # If true, sort tables before syncing as per foreign key dependencies.
59
+ # (Dependent tables are synced last to reduce risk of foreign key
60
+ # constraint violations.)
61
+ # * :+scan_progress_printer+:
62
+ # The progress printer key as registered by ScanProgressPrinters#register.
63
+ # Determines how the scan progress is visualized.
64
+ # * :+use_ansi+: Only use ANSI codes for text output if +true+.
65
+ # * :+auto_key_limit+:
66
+ # If a table has no primary keys and no primary keys have been specified
67
+ # manually using the :+primary_key_names+ option, then this option can be
68
+ # activated to simply use all columns of the table as a big combined key.
69
+ # This option specifies up to how many columns a table may have in order
70
+ # to use them as one big, combined primary key.
71
+ # Typical use case: the database has a lot of tables to map many-to-many
72
+ # relationshipts and no combined primary key is set up for them.
73
+ # Sync specific settings
74
+ # * :+before_table_sync+:
75
+ # A hook that is executed before a table sync.
76
+ # Can be either
77
+ # * a String: executed as SQL command on both databases.
78
+ # * a Proc:
79
+ # Called once before the table sync.
80
+ # The Proc is called with one parameter: the current SyncHelper instance.
81
+ # Through the sync helper there is access to the name of the synced table,
82
+ # the current session, etc
83
+ # Example:
84
+ # lambda {|helper| $stderr.puts "Hook called for #{helper.left_table}."}
85
+ # * :+after_table_sync+:
86
+ # Same as :+before_table_sync+ (but called after the sync is completed).
87
+ # * :+syncer+:
88
+ # A syncer key as registered by TableSync#register_syncer.
89
+ # Determines which sync algorithm is used.
90
+ # * further options as defined by each syncer
91
+ # Replication specific settings:
92
+ # * :+rep_prefix+: the prefix that is put in front of all created database objects
93
+ # * :+key_sep+: which string separates columns in the key column of the change log table
94
+ # * :+replicator+:
95
+ # Determines which replicator algorithm to use.
96
+ # For each replicator must also exist a corresponding +:syncer+. (It is
97
+ # used for the initial sync of a table.)
98
+ # If no +:syncer+ option is specified, than a syncer as named by this
99
+ # option is used.
100
+ # * :+adjust_sequences+:
101
+ # If true, adjust sequences to avoid number conflicts between left and
102
+ # right database during replication.
103
+ # * :+sequence_adjustement_buffer+:
104
+ # When updating a sequence, this is the additional gap to avoid sequence
105
+ # conflicts to appear due to concurrent record insertions.
106
+ # * :+sequence_increment+: new sequence value = last sequence value + this
107
+ # * :+left_sequence_offset+, +right_sequence_offset+:
108
+ # Default sequence offset for the table in the according data base.
109
+ # E. g. with a +sequence_increment+ of 2, an offset of 0 will produce even,
110
+ # an offset of 1 will produce odd numbers.
111
+ # * :+replication_interval+: time in seconds between replication runs
112
+ attr_reader :options
113
+
114
+ # Merges the specified +options+ hash into the existing options
115
+ def options=(options)
116
+ @options ||= {}
117
+ @options = @options.merge! options
118
+ end
119
+
120
+ # Array of table specifications for tables that should be processed
121
+ # Refer to #add_table_options for what constitutes a valid table specification.
122
+ def included_table_specs
123
+ @included_table_specs ||= []
124
+ end
125
+
126
+ # Array of table specifications for tables that should *not* be processed
127
+ # Refer to #add_table_options for what constitutes a valid table specification.
128
+ def excluded_table_specs
129
+ @excluded_table_specs ||= []
130
+ end
131
+
132
+ # Ensures that rubyrep infrastructure tables are excluded
133
+ def exclude_rubyrep_tables
134
+ exclude_tables Regexp.new("^#{options[:rep_prefix]}_.*")
135
+ end
136
+
137
+ # A list of tables having table specific options that should be considered
138
+ # during processing (scanned, synced, ...)
139
+ # +tables_with_options+ is a 2 element array with
140
+ # * first element: A +table_spec+ (either a table name or a regexp matching multiple tables)
141
+ # * second element: The +options+ hash (detailed format described in #add_tables
142
+ # Should only be accessed via #add_table_options and #options_for_table
143
+ def tables_with_options
144
+ @tables_with_options ||= []
145
+ end
146
+
147
+ # Adds the specified tables to the list of tables that should be processed.
148
+ # If options are provided, store them for future processing.
149
+ # Refer to #add_table_options for detailed description of parameters.
150
+ def include_tables(table_spec, options = nil)
151
+ included_table_specs << table_spec unless included_table_specs.include?(table_spec)
152
+ add_table_options(table_spec, options) if options
153
+ end
154
+ alias_method :include_table, :include_tables
155
+
156
+ # Excludes the specified table from the list of tables that should be
157
+ # processed.
158
+ # Refer to #add_table_options for detailed description of what constitutes a
159
+ # valid table specification.
160
+ def exclude_tables(table_spec)
161
+ excluded_table_specs << table_spec unless excluded_table_specs.include?(table_spec)
162
+ end
163
+ alias_method :exclude_table, :exclude_tables
164
+
165
+ # Adds the specified options for the provided +table_spec+.
166
+ # A +table_spec+ can be either
167
+ # * a table name or
168
+ # * a table pair (e. g. "my_left_table, my_right_table")
169
+ # * a regexp matching multiple tables.
170
+ # +options+ is hash with possible generic values as described under #options.
171
+ # Additional, exclusively table specific options:
172
+ # * :+primary_key_names+: array of primary key names
173
+ def add_table_options(table_spec, options)
174
+ i = nil
175
+ tables_with_options.each_with_index { |table_options, k|
176
+ i = k if table_options[0] == table_spec
177
+ }
178
+ if i
179
+ table_options = tables_with_options[i][1]
180
+ else
181
+ table_options = {}
182
+ tables_with_options << [table_spec, table_options]
183
+ end
184
+ table_options.merge! options
185
+ end
186
+ alias_method :add_table_option, :add_table_options
187
+
188
+ # Yields all table specs that have been set up with the given option
189
+ # * +key+: the option key
190
+ # Yields:
191
+ # * +table_spec+: the table specification of the matching option (or nil if non-table specific setting)
192
+ # * +option_value+: the option value for the specified +key+
193
+ def each_matching_option(key)
194
+ yield nil, options[key] if options.include?(key)
195
+ tables_with_options.each do |table_options|
196
+ yield table_options[0], table_options[1][key] if table_options[1].include? key
197
+ end
198
+ end
199
+
200
+ # Returns an option hash for the given table.
201
+ # Accumulates options for all matching table specs (most recently added options
202
+ # overwrite according options added before).
203
+ #
204
+ # Also includes the general options as returned by #options.
205
+ # (Table specific options overwrite the general options).
206
+ #
207
+ # Possible option values are described under #add_tables.
208
+ def options_for_table(table)
209
+ resulting_options = options.clone
210
+ tables_with_options.each do |table_options|
211
+ match = false
212
+ if table_options[0].kind_of? Regexp
213
+ match = (table_options[0] =~ table)
214
+ else
215
+ match = (table_options[0].sub(/(^.*),.*/,'\1').strip == table)
216
+ end
217
+ resulting_options.merge! table_options[1] if match
218
+ end
219
+
220
+ # Merge the default syncer options in (if syncer has some)
221
+ syncer_class = Syncers.configured_syncer(resulting_options)
222
+ if syncer_class.respond_to? :default_options
223
+ default_syncer_options = syncer_class.default_options.clone
224
+ else
225
+ default_syncer_options = {}
226
+ end
227
+ resulting_options = default_syncer_options.merge! resulting_options
228
+
229
+ resulting_options
230
+ end
231
+
232
+ # initialize configuration settings
233
+ def initialize
234
+ self.left = {}
235
+ self.right = {}
236
+ self.options = DEFAULT_OPTIONS.clone
237
+ end
238
+
239
+ end
240
+ end