mysql-replication-helper 0.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/.document +5 -0
- data/README.rdoc +27 -0
- data/Rakefile +55 -0
- data/bin/replication-helper +25 -0
- data/lib/mysql_replication_helper.rb +10 -0
- data/lib/mysql_replication_helper/agent.rb +51 -0
- data/lib/mysql_replication_helper/agent/master.rb +28 -0
- data/lib/mysql_replication_helper/agent/slave.rb +68 -0
- data/lib/mysql_replication_helper/daemon.rb +18 -0
- data/lib/mysql_replication_helper/daemon_launcher.rb +60 -0
- data/lib/mysql_replication_helper/error_handler.rb +24 -0
- data/mysql-replication-helper.gemspec +52 -0
- data/test/mysql_replication_helper/error_handler_test.rb +28 -0
- data/test/mysql_replication_helper_test.rb +6 -0
- data/test/test_helper.rb +9 -0
- metadata +72 -0
data/.document
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
= mysql-replication-helper
|
2
|
+
|
3
|
+
This is a script that mitigates the errors generated by a Master/Slave MySQL
|
4
|
+
replication configuration.
|
5
|
+
|
6
|
+
A slave database engine needs to be updated in tandem with the master to
|
7
|
+
account for changes such as:
|
8
|
+
|
9
|
+
* Adding a new database
|
10
|
+
* Adding a new user
|
11
|
+
* Adding views which reference particular users
|
12
|
+
|
13
|
+
The replication helper facilitates this by identifying typical errors and
|
14
|
+
fixing them.
|
15
|
+
|
16
|
+
== Installing
|
17
|
+
|
18
|
+
% gem sources -a http://gems.github.com
|
19
|
+
% sudo gem install theworkinggroup-mysql-replication-helper
|
20
|
+
|
21
|
+
== Starting
|
22
|
+
|
23
|
+
% replication-helper --daemon
|
24
|
+
|
25
|
+
== Copyright
|
26
|
+
|
27
|
+
Copyright (c) 2009 The Working Group Inc. (http://twg.ca/)
|
data/Rakefile
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "mysql-replication-helper"
|
8
|
+
gem.summary = %Q{MySQL Replication Helper}
|
9
|
+
gem.email = "github@tadman.ca"
|
10
|
+
gem.homepage = "http://github.com/theworkinggroup/mysql-replication-helper"
|
11
|
+
gem.authors = [ 'Scott Tadman' ]
|
12
|
+
gem.executables = %w[ replication-helper ]
|
13
|
+
gem.add_dependency 'daemons'
|
14
|
+
|
15
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
16
|
+
end
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'rake/testtask'
|
22
|
+
Rake::TestTask.new(:test) do |test|
|
23
|
+
test.libs << 'lib' << 'test'
|
24
|
+
test.pattern = 'test/**/*_test.rb'
|
25
|
+
test.verbose = true
|
26
|
+
end
|
27
|
+
|
28
|
+
begin
|
29
|
+
require 'rcov/rcovtask'
|
30
|
+
Rcov::RcovTask.new do |test|
|
31
|
+
test.libs << 'test'
|
32
|
+
test.pattern = 'test/**/*_test.rb'
|
33
|
+
test.verbose = true
|
34
|
+
end
|
35
|
+
rescue LoadError
|
36
|
+
task :rcov do
|
37
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
task :default => :test
|
42
|
+
|
43
|
+
require 'rake/rdoctask'
|
44
|
+
Rake::RDocTask.new do |rdoc|
|
45
|
+
if File.exist?('VERSION')
|
46
|
+
version = File.read('VERSION').chomp
|
47
|
+
else
|
48
|
+
version = ""
|
49
|
+
end
|
50
|
+
|
51
|
+
rdoc.rdoc_dir = 'rdoc'
|
52
|
+
rdoc.title = "mysql-replication-helper #{version}"
|
53
|
+
rdoc.rdoc_files.include('README*')
|
54
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
55
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# replication-helper MySQL replication helper agent
|
4
|
+
#
|
5
|
+
# chkconfig: - 65 34
|
6
|
+
# description: MySQL replication helper agent
|
7
|
+
# processname: replication-helper
|
8
|
+
# config: /etc/replication-helper.conf
|
9
|
+
# pidfile: /var/run/replication-helper.pid
|
10
|
+
#
|
11
|
+
|
12
|
+
require 'rubygems'
|
13
|
+
require 'daemons'
|
14
|
+
|
15
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
16
|
+
|
17
|
+
require 'mysql_replication_helper'
|
18
|
+
|
19
|
+
daemon_script_path = File.expand_path('../lib/mysql_replication_helper/daemon_launcher.rb', File.dirname(__FILE__))
|
20
|
+
|
21
|
+
if (!File.exist?(daemon_script_path))
|
22
|
+
daemon_script_path = Gem.required_location('mysql_replication_helper','mysql_replication_helper/daemon_launcher.rb')
|
23
|
+
end
|
24
|
+
|
25
|
+
Daemons.run(daemon_script_path)
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'mysql'
|
3
|
+
|
4
|
+
module MysqlReplicationHelper
|
5
|
+
# == Autoloads ============================================================
|
6
|
+
|
7
|
+
autoload(:Agent, 'mysql_replication_helper/agent')
|
8
|
+
autoload(:ErrorHandler, 'mysql_replication_helper/error_handler')
|
9
|
+
autoload(:Daemon, 'mysql_replication_helper/daemon')
|
10
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module MysqlReplicationHelper
|
2
|
+
class Agent
|
3
|
+
autoload(:Master, 'mysql_replication_helper/agent/master')
|
4
|
+
autoload(:Slave, 'mysql_replication_helper/agent/slave')
|
5
|
+
|
6
|
+
DEFAULT_OPTIONS = {
|
7
|
+
:host => 'localhost',
|
8
|
+
:user => 'root',
|
9
|
+
:master_socket => '/local/db/mysql.sock',
|
10
|
+
:slave_socket => '/ebs/db/mysql.sock'
|
11
|
+
}
|
12
|
+
|
13
|
+
def initialize(options)
|
14
|
+
@options = with_default_options(options)
|
15
|
+
end
|
16
|
+
|
17
|
+
def connection
|
18
|
+
@connection ||=
|
19
|
+
Mysql.real_connect(
|
20
|
+
@options[:host],
|
21
|
+
user_name,
|
22
|
+
@options[:password],
|
23
|
+
@options[:db],
|
24
|
+
@options[:port],
|
25
|
+
socket_name
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
def user_name
|
30
|
+
@options[:user]
|
31
|
+
end
|
32
|
+
|
33
|
+
def socket_name
|
34
|
+
@options[:socket]
|
35
|
+
end
|
36
|
+
|
37
|
+
def with_default_options(options)
|
38
|
+
DEFAULT_OPTIONS.merge(options)
|
39
|
+
end
|
40
|
+
|
41
|
+
def execute(statement)
|
42
|
+
STDERR.puts(statement)
|
43
|
+
connection.real_query(statement)
|
44
|
+
end
|
45
|
+
|
46
|
+
def query(statement)
|
47
|
+
STDERR.puts(statement)
|
48
|
+
connection.query(statement)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module MysqlReplicationHelper
|
2
|
+
class Agent
|
3
|
+
class Master < Agent
|
4
|
+
def poll!
|
5
|
+
# Nothing yet
|
6
|
+
end
|
7
|
+
|
8
|
+
def master_status
|
9
|
+
row = query("SHOW MASTER STATUS").fetch_row
|
10
|
+
|
11
|
+
return unless (row)
|
12
|
+
|
13
|
+
{
|
14
|
+
:master_log_file => row[0],
|
15
|
+
:master_log_position => row[1].to_i
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def user_name
|
20
|
+
@options[:master_user] or super
|
21
|
+
end
|
22
|
+
|
23
|
+
def socket_name
|
24
|
+
@options[:master_socket] or super
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module MysqlReplicationHelper
|
2
|
+
class Agent
|
3
|
+
class Slave < Agent
|
4
|
+
include MysqlReplicationHelper::ErrorHandler
|
5
|
+
|
6
|
+
def poll!
|
7
|
+
if (configured?)
|
8
|
+
if (error_message = slave_error)
|
9
|
+
if (statements = sql_to_recover_from(error_message))
|
10
|
+
statements.each do |sql|
|
11
|
+
connection.real_query(sql)
|
12
|
+
end
|
13
|
+
else
|
14
|
+
# Unrecoverable error?
|
15
|
+
end
|
16
|
+
end
|
17
|
+
else
|
18
|
+
assign_master(@options[:master])
|
19
|
+
start!
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def configured?
|
24
|
+
!!query("SHOW SLAVE STATUS").fetch_row
|
25
|
+
end
|
26
|
+
|
27
|
+
def slave_error
|
28
|
+
row = query("SHOW SLAVE STATUS").fetch_row
|
29
|
+
|
30
|
+
row and row[19]
|
31
|
+
end
|
32
|
+
|
33
|
+
def assign_master(master)
|
34
|
+
master_status = master.master_status
|
35
|
+
|
36
|
+
master_options =
|
37
|
+
{
|
38
|
+
'MASTER_HOST' => 'localhost',
|
39
|
+
'MASTER_USER' => master.user_name,
|
40
|
+
'MASTER_PORT' => 3306,
|
41
|
+
'MASTER_LOG_FILE' => master_status[:master_log_file],
|
42
|
+
'MASTER_LOG_POS' => master_status[:master_log_position]
|
43
|
+
}.collect do |k, v|
|
44
|
+
case (v)
|
45
|
+
when String:
|
46
|
+
"#{k}='#{Mysql.quote(v)}'"
|
47
|
+
else
|
48
|
+
"#{k}=#{v}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
execute("CHANGE MASTER TO #{master_options * ', '}")
|
53
|
+
end
|
54
|
+
|
55
|
+
def start!
|
56
|
+
execute("START SLAVE")
|
57
|
+
end
|
58
|
+
|
59
|
+
def user_name
|
60
|
+
@options[:slave_user] or super
|
61
|
+
end
|
62
|
+
|
63
|
+
def socket_name
|
64
|
+
@options[:slave_socket] or super
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module MysqlReplicationHelper
|
2
|
+
class Daemon
|
3
|
+
def initialize(options)
|
4
|
+
@options = options
|
5
|
+
|
6
|
+
@options[:master] = @master = Agent::Master.new(options)
|
7
|
+
@options[:slave] = @slave = Agent::Slave.new(options)
|
8
|
+
end
|
9
|
+
|
10
|
+
def run!
|
11
|
+
while (true)
|
12
|
+
@master.poll!
|
13
|
+
@slave.poll!
|
14
|
+
sleep(10)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Daemonize module
|
4
|
+
#
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
require 'yaml'
|
8
|
+
require 'optparse'
|
9
|
+
|
10
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..'))
|
11
|
+
|
12
|
+
require 'mysql_replication_helper'
|
13
|
+
|
14
|
+
# == Constants ==============================================================
|
15
|
+
|
16
|
+
CONFIG_FILE_LOCATIONS = [
|
17
|
+
"/etc/replication-helper.conf",
|
18
|
+
"/etc/replication-helper/config",
|
19
|
+
"~/.replication-helper/config"
|
20
|
+
].collect { |p| File.expand_path(p) }.freeze
|
21
|
+
|
22
|
+
DEFAULT_CONFIG = {
|
23
|
+
}
|
24
|
+
|
25
|
+
# == Options ================================================================
|
26
|
+
|
27
|
+
op = OptionParser.new
|
28
|
+
options = { }
|
29
|
+
config = { }
|
30
|
+
config_file = nil
|
31
|
+
|
32
|
+
op.on("--master-socket=s") { |socket| options[:master_socket] = socket }
|
33
|
+
op.on("--master-data=s") { |dir| options[:master_data] = dir }
|
34
|
+
op.on("--master-user=s") { |name| options[:master_user] = name }
|
35
|
+
|
36
|
+
op.on("--slave-socket=s") { |socket| options[:slave_socket] = socket }
|
37
|
+
op.on("--slave-data=s") { |dir| options[:slave_data] = dir }
|
38
|
+
op.on("--slave-user=s") { |name| options[:slave_user] = name }
|
39
|
+
|
40
|
+
op.on("-c", "--config=s") { |path| config_file = nil }
|
41
|
+
op.on("-v", "--verbose") { options[:verbose] = true }
|
42
|
+
op.on("-h", "--help") { show_help }
|
43
|
+
|
44
|
+
args = op.parse(*ARGV)
|
45
|
+
|
46
|
+
# == Configuration ==========================================================
|
47
|
+
|
48
|
+
[ CONFIG_FILE_LOCATIONS, config_file ].flatten.each do |config_file|
|
49
|
+
if (File.exist?(config_file))
|
50
|
+
config = YAML.load(open(config_file))
|
51
|
+
break
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
config = DEFAULT_CONFIG.merge(config.inject({ }) { |h,(k,v)| h[k.to_sym] = v; h }).merge(options)
|
56
|
+
|
57
|
+
|
58
|
+
# == Main ===================================================================
|
59
|
+
|
60
|
+
MysqlReplicationHelper::Daemon.new(config).run!
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module MysqlReplicationHelper
|
2
|
+
module ErrorHandler
|
3
|
+
def sql_to_recover_from(error)
|
4
|
+
case (error)
|
5
|
+
when /^Error 'Unknown database '([^\']+)'' on query/
|
6
|
+
[
|
7
|
+
"CREATE DATABASE `#{$1}`",
|
8
|
+
"START SLAVE"
|
9
|
+
]
|
10
|
+
when /^Error 'There is no '([^\']+)'@'([^\']+)' registered' on query. Default database: '([^\']+)'./
|
11
|
+
[
|
12
|
+
"CREATE USER `#{$1}`@`#{$2}`",
|
13
|
+
"GRANT ALL PRIVILEGES ON `#{$3}`.* TO `#{$1}`@`#{$2}`",
|
14
|
+
"START SLAVE"
|
15
|
+
]
|
16
|
+
when /^Error 'View '[^\']+' references invalid table\(s\) or column\(s\) or function\(s\) or definer\/invoker of view lack rights to use them' on query. Default database: '([^\']+)'. Query: 'CREATE .*? DEFINER=`([^\`]+)`@`([^\`]+)`/
|
17
|
+
[
|
18
|
+
"GRANT ALL PRIVILEGES ON `#{$1}`.* TO `#{$2}`@`#{$3}`",
|
19
|
+
"START SLAVE"
|
20
|
+
]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{mysql-replication-helper}
|
8
|
+
s.version = "0.2.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = [%q{Scott Tadman}]
|
12
|
+
s.date = %q{2011-08-18}
|
13
|
+
s.email = %q{github@tadman.ca}
|
14
|
+
s.executables = [%q{replication-helper}]
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README.rdoc"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
".document",
|
20
|
+
"README.rdoc",
|
21
|
+
"Rakefile",
|
22
|
+
"bin/replication-helper",
|
23
|
+
"lib/mysql_replication_helper.rb",
|
24
|
+
"lib/mysql_replication_helper/agent.rb",
|
25
|
+
"lib/mysql_replication_helper/agent/master.rb",
|
26
|
+
"lib/mysql_replication_helper/agent/slave.rb",
|
27
|
+
"lib/mysql_replication_helper/daemon.rb",
|
28
|
+
"lib/mysql_replication_helper/daemon_launcher.rb",
|
29
|
+
"lib/mysql_replication_helper/error_handler.rb",
|
30
|
+
"mysql-replication-helper.gemspec",
|
31
|
+
"test/mysql_replication_helper/error_handler_test.rb",
|
32
|
+
"test/mysql_replication_helper_test.rb",
|
33
|
+
"test/test_helper.rb"
|
34
|
+
]
|
35
|
+
s.homepage = %q{http://github.com/theworkinggroup/mysql-replication-helper}
|
36
|
+
s.require_paths = [%q{lib}]
|
37
|
+
s.rubygems_version = %q{1.8.8}
|
38
|
+
s.summary = %q{MySQL Replication Helper}
|
39
|
+
|
40
|
+
if s.respond_to? :specification_version then
|
41
|
+
s.specification_version = 3
|
42
|
+
|
43
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
44
|
+
s.add_runtime_dependency(%q<daemons>, [">= 0"])
|
45
|
+
else
|
46
|
+
s.add_dependency(%q<daemons>, [">= 0"])
|
47
|
+
end
|
48
|
+
else
|
49
|
+
s.add_dependency(%q<daemons>, [">= 0"])
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/../test_helper")
|
2
|
+
|
3
|
+
class TestHandler
|
4
|
+
include MysqlReplicationHelper::ErrorHandler
|
5
|
+
end
|
6
|
+
|
7
|
+
class MysqlReplicationHelper::ErrorHandlerTest < Test::Unit::TestCase
|
8
|
+
def test_responses
|
9
|
+
handler = TestHandler.new
|
10
|
+
|
11
|
+
[
|
12
|
+
[
|
13
|
+
"Error 'Unknown database 'example_db'' on query. Default database: 'example_db'.",
|
14
|
+
[ "CREATE DATABASE `example_db`", "START SLAVE" ]
|
15
|
+
],
|
16
|
+
[
|
17
|
+
"Error 'There is no 'example_user'@'example_host' registered' on query. Default database: 'example_db'.",
|
18
|
+
[ "CREATE USER `example_user`@`example_host`", "GRANT ALL PRIVILEGES ON `example_db`.* TO `example_user`@`example_host`", "START SLAVE" ]
|
19
|
+
],
|
20
|
+
[
|
21
|
+
"Error 'View 'example_db.example_table' references invalid table(s) or column(s) or function(s) or definer/invoker of view lack rights to use them' on query. Default database: 'example_db'. Query: 'CREATE ALGORITHM=UNDEFINED DEFINER=`example_user`@`example_host` SQL SECURITY DEFINER VIEW",
|
22
|
+
[ "GRANT ALL PRIVILEGES ON `example_db`.* TO `example_user`@`example_host`", "START SLAVE" ]
|
23
|
+
]
|
24
|
+
].each do |test_case|
|
25
|
+
assert_equal test_case[1], handler.sql_to_recover_from(test_case[0])
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mysql-replication-helper
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Scott Tadman
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-08-18 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: daemons
|
16
|
+
requirement: &70228311548780 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70228311548780
|
25
|
+
description:
|
26
|
+
email: github@tadman.ca
|
27
|
+
executables:
|
28
|
+
- replication-helper
|
29
|
+
extensions: []
|
30
|
+
extra_rdoc_files:
|
31
|
+
- README.rdoc
|
32
|
+
files:
|
33
|
+
- .document
|
34
|
+
- README.rdoc
|
35
|
+
- Rakefile
|
36
|
+
- bin/replication-helper
|
37
|
+
- lib/mysql_replication_helper.rb
|
38
|
+
- lib/mysql_replication_helper/agent.rb
|
39
|
+
- lib/mysql_replication_helper/agent/master.rb
|
40
|
+
- lib/mysql_replication_helper/agent/slave.rb
|
41
|
+
- lib/mysql_replication_helper/daemon.rb
|
42
|
+
- lib/mysql_replication_helper/daemon_launcher.rb
|
43
|
+
- lib/mysql_replication_helper/error_handler.rb
|
44
|
+
- mysql-replication-helper.gemspec
|
45
|
+
- test/mysql_replication_helper/error_handler_test.rb
|
46
|
+
- test/mysql_replication_helper_test.rb
|
47
|
+
- test/test_helper.rb
|
48
|
+
homepage: http://github.com/theworkinggroup/mysql-replication-helper
|
49
|
+
licenses: []
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options: []
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ! '>='
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0'
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
requirements: []
|
67
|
+
rubyforge_project:
|
68
|
+
rubygems_version: 1.8.8
|
69
|
+
signing_key:
|
70
|
+
specification_version: 3
|
71
|
+
summary: MySQL Replication Helper
|
72
|
+
test_files: []
|