mysql2mysql 0.0.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/Changelog +2 -0
- data/INSTALL +9 -0
- data/README.rdoc +20 -0
- data/lib/mysql2mysql.rb +1 -0
- data/lib/mysql_2_mysql.rb +277 -0
- metadata +97 -0
data/Changelog
ADDED
data/INSTALL
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
== Description
|
2
|
+
|
3
|
+
dump table's structure and data between mysql servers and databases.
|
4
|
+
|
5
|
+
== Example
|
6
|
+
require 'rubygems'
|
7
|
+
require 'mysql2mysql'
|
8
|
+
Mysql2Mysql.new.
|
9
|
+
from('mysql://mysql.server1/test?user=root&password=pass').
|
10
|
+
to('mysql://mysql.server2/test?user=root&password=pass').
|
11
|
+
tables('test' => :all, 'mysql' => /^time_zone/).
|
12
|
+
dump(:charset => 'utf8', 'with_data' => true)
|
13
|
+
|
14
|
+
== Version
|
15
|
+
|
16
|
+
v0.0.1
|
17
|
+
|
18
|
+
== Author
|
19
|
+
|
20
|
+
xianhua.zhou<xianhua.zhou@gmail.com>
|
data/lib/mysql2mysql.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'mysql_2_mysql'
|
@@ -0,0 +1,277 @@
|
|
1
|
+
#
|
2
|
+
# == Description
|
3
|
+
#
|
4
|
+
# dump table's structure and data between mysql servers and databases.
|
5
|
+
#
|
6
|
+
# == Example
|
7
|
+
#
|
8
|
+
# Please read README.rdoc
|
9
|
+
#
|
10
|
+
# == Version
|
11
|
+
#
|
12
|
+
# v0.0.1
|
13
|
+
#
|
14
|
+
# == Author
|
15
|
+
#
|
16
|
+
# xianhua.zhou<xianhua.zhou@gmail.com>
|
17
|
+
#
|
18
|
+
require 'sequel'
|
19
|
+
|
20
|
+
class Mysql2Mysql
|
21
|
+
|
22
|
+
VERSION = '0.0.1'
|
23
|
+
|
24
|
+
def self.version
|
25
|
+
VERSION
|
26
|
+
end
|
27
|
+
|
28
|
+
@@methods = %w(from to tables exclude)
|
29
|
+
|
30
|
+
def initialize(opts = {})
|
31
|
+
@@methods.each do |method_name|
|
32
|
+
send method_name, opts[method_name.to_sym] or opts[method_name.to_s]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
@@methods.each do |method_name|
|
37
|
+
class_eval %Q{
|
38
|
+
def #{method_name}(#{method_name})
|
39
|
+
@#{method_name} = #{method_name}
|
40
|
+
self
|
41
|
+
end
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
def dump(opts = {})
|
46
|
+
# initialize database connections
|
47
|
+
init_db_connection
|
48
|
+
|
49
|
+
# initialize opts
|
50
|
+
opts = dump_opts opts
|
51
|
+
|
52
|
+
# before all callback
|
53
|
+
before_dump opts
|
54
|
+
|
55
|
+
tables_list.each do |database, tables|
|
56
|
+
tables.each do |table|
|
57
|
+
|
58
|
+
# before each callback
|
59
|
+
if opts[:before_each].respond_to? :call
|
60
|
+
to_database, to_table = opts[:before_each].call(database, table)
|
61
|
+
end
|
62
|
+
to_database ||= database
|
63
|
+
to_table ||= table
|
64
|
+
|
65
|
+
dump_table database, table, to_database, to_table, opts
|
66
|
+
|
67
|
+
# after each callback
|
68
|
+
if opts[:after_each].respond_to? :call
|
69
|
+
opts[:after_each].call(database, table)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# after all callback
|
75
|
+
after_dump opts
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def dump_opts(opts)
|
82
|
+
{
|
83
|
+
# it's used for "SET NAMES #{charset}"
|
84
|
+
:charset => 'utf8',
|
85
|
+
|
86
|
+
# dump data or just table structure
|
87
|
+
:with_data => true,
|
88
|
+
|
89
|
+
# drop the table before do dump the table
|
90
|
+
:drop_table_first => false,
|
91
|
+
|
92
|
+
# callbacks
|
93
|
+
:before_all => nil,
|
94
|
+
:after_all => nil,
|
95
|
+
:before_each => nil,
|
96
|
+
:after_each => nil
|
97
|
+
}.merge(opts)
|
98
|
+
end
|
99
|
+
|
100
|
+
def before_dump(opts)
|
101
|
+
# prepare dump
|
102
|
+
[
|
103
|
+
"SET NAMES #{opts[:charset]}",
|
104
|
+
"SET FOREIGN_KEY_CHECKS = 0",
|
105
|
+
"SET UNIQUE_CHECKS = 0"
|
106
|
+
].each do |sql|
|
107
|
+
run_sql sql, :on_connection => @to_db
|
108
|
+
end
|
109
|
+
opts[:before_all].call(@from_db, @to_db) if opts[:after_all].respond_to? :call
|
110
|
+
end
|
111
|
+
|
112
|
+
def after_dump(opts)
|
113
|
+
# clean up
|
114
|
+
[
|
115
|
+
"SET FOREIGN_KEY_CHECKS = 1",
|
116
|
+
"SET UNIQUE_CHECKS = 1"
|
117
|
+
].each do |sql|
|
118
|
+
run_sql sql, :on_connection => @to_db
|
119
|
+
end
|
120
|
+
opts[:after_all].call(@from_db, @to_db) if opts[:after_all].respond_to? :call
|
121
|
+
end
|
122
|
+
|
123
|
+
def dump_table(from_database, from_table, to_database, to_table, opts = {})
|
124
|
+
use_db from_database, :on_connection => @from_db
|
125
|
+
create_table_ddl = @from_db.fetch("SHOW CREATE TABLE #{from_table}").first[:'Create Table']
|
126
|
+
|
127
|
+
# create database
|
128
|
+
begin
|
129
|
+
use_db to_database, :on_connection => @to_db
|
130
|
+
rescue Sequel::DatabaseError => e
|
131
|
+
begin
|
132
|
+
run_sql "CREATE DATABASE #{to_database}", :on_connection => @to_db
|
133
|
+
use_db to_database, :on_connection => @to_db
|
134
|
+
rescue Exception => e
|
135
|
+
raise Mysql2MysqlException.new "create database #{to_database} failed\n DSN info: #{@to_db}"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# create table
|
140
|
+
unless @to_db.table_exists? to_table.to_sym
|
141
|
+
begin
|
142
|
+
run_sql create_table_ddl.gsub("`#{from_table}`", "`#{to_table}`"), :on_connection => @to_db
|
143
|
+
rescue Exception => e
|
144
|
+
raise Mysql2MysqlException.new "create table #{to_table} failed in the database #{to_database}\n DSN info: #{@to_db}\n: message: #{e}"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
return unless opts[:with_data]
|
149
|
+
|
150
|
+
# dump data
|
151
|
+
@from_db.fetch("SELECT * FROM #{from_table}").each do |row|
|
152
|
+
@to_db[to_table.to_sym].insert row
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def run_sql(sql, opts)
|
157
|
+
opts[:on_connection].run sql
|
158
|
+
end
|
159
|
+
|
160
|
+
def use_db(db, opts)
|
161
|
+
run_sql "use #{db}", opts
|
162
|
+
end
|
163
|
+
|
164
|
+
def init_db_connection
|
165
|
+
@from_db = db_connection @from
|
166
|
+
@to_db = db_connection @to
|
167
|
+
end
|
168
|
+
|
169
|
+
def db_connection(dsn)
|
170
|
+
dsn[:adapter] = 'mysql' if dsn.is_a? Hash
|
171
|
+
Sequel.connect(dsn)
|
172
|
+
end
|
173
|
+
|
174
|
+
def tables_list
|
175
|
+
raise Mysql2MysqlException.new 'No tables need to dump' if @tables.nil?
|
176
|
+
filter_tables
|
177
|
+
end
|
178
|
+
|
179
|
+
def all_databases
|
180
|
+
@from_db.fetch('SHOW DATABASES').collect do |row|
|
181
|
+
row[:Database]
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def filter_tables
|
186
|
+
all_valid_tables = \
|
187
|
+
if is_all? @tables
|
188
|
+
all_databases.inject({}) do |all_tables, dbname|
|
189
|
+
all_tables.merge dbname => get_tables_by_db(dbname)
|
190
|
+
end
|
191
|
+
else
|
192
|
+
all_tables = {}
|
193
|
+
all_databases.each do |dbname|
|
194
|
+
@tables.each do |orig_dbname, orig_tbname|
|
195
|
+
next unless is_eql_or_match?(orig_dbname, dbname)
|
196
|
+
|
197
|
+
tables = get_tables_by_db dbname
|
198
|
+
|
199
|
+
if is_all? orig_tbname
|
200
|
+
all_tables[dbname] = tables
|
201
|
+
break
|
202
|
+
end
|
203
|
+
|
204
|
+
orig_tbnames = [orig_tbname] unless orig_tbname.is_a? Array
|
205
|
+
tables = tables.find_all do |tbname|
|
206
|
+
orig_tbnames.find do |orig_tbname|
|
207
|
+
is_eql_or_match?(orig_tbname, tbname)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
all_tables[dbname] = tables
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
all_tables
|
216
|
+
end
|
217
|
+
|
218
|
+
filter all_valid_tables
|
219
|
+
end
|
220
|
+
|
221
|
+
def is_eql_or_match?(origin, current)
|
222
|
+
if origin.is_a? Regexp
|
223
|
+
origin.match current
|
224
|
+
else
|
225
|
+
origin.to_s == current.to_s
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def is_all?(items)
|
230
|
+
[:all, '*'].include? items
|
231
|
+
end
|
232
|
+
|
233
|
+
def get_tables_by_db(dbname)
|
234
|
+
use_db dbname, :on_connection => @from_db
|
235
|
+
@from_db.tables
|
236
|
+
end
|
237
|
+
|
238
|
+
def filter(origin_tables)
|
239
|
+
need_exclude = lambda do |origin, exclude|
|
240
|
+
is_eql_or_match? origin.to_s, exclude
|
241
|
+
end
|
242
|
+
|
243
|
+
return origin_tables if @exclude.nil?
|
244
|
+
|
245
|
+
exclude_tables = case @exclude
|
246
|
+
when String
|
247
|
+
{@exclude => '*'}
|
248
|
+
when Array
|
249
|
+
@exclude.inject({}) do |items, it|
|
250
|
+
items[it] = '*'
|
251
|
+
end
|
252
|
+
when Hash
|
253
|
+
@exclude
|
254
|
+
else
|
255
|
+
raise Mysql2MysqlException.new 'Invalid exclude parameters given'
|
256
|
+
end
|
257
|
+
|
258
|
+
reject_action = lambda do |dbname, tbname|
|
259
|
+
exclude_tables.each do |exclude_dbname, exclude_tbname|
|
260
|
+
if need_exclude.call(dbname, exclude_dbname)
|
261
|
+
if is_all?(exclude_tbname) or need_exclude.call(tbname, exclude_tbname)
|
262
|
+
return true
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
false
|
268
|
+
end
|
269
|
+
|
270
|
+
origin_tables.reject do |dbname, tbname|
|
271
|
+
reject_action.call(dbname, tbname)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
class Mysql2MysqlException < Exception
|
277
|
+
end
|
metadata
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mysql2mysql
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- xianhua.zhou
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-11-18 00:00:00 +08:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: sequel
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 3
|
30
|
+
- 13
|
31
|
+
- 0
|
32
|
+
version: 3.13.0
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: rspec
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
segments:
|
44
|
+
- 2
|
45
|
+
- 0
|
46
|
+
- 1
|
47
|
+
version: 2.0.1
|
48
|
+
type: :development
|
49
|
+
version_requirements: *id002
|
50
|
+
description:
|
51
|
+
email: xianhua.zhou@gmail.com
|
52
|
+
executables: []
|
53
|
+
|
54
|
+
extensions: []
|
55
|
+
|
56
|
+
extra_rdoc_files: []
|
57
|
+
|
58
|
+
files:
|
59
|
+
- Changelog
|
60
|
+
- README.rdoc
|
61
|
+
- INSTALL
|
62
|
+
- lib/mysql2mysql.rb
|
63
|
+
- lib/mysql_2_mysql.rb
|
64
|
+
has_rdoc: true
|
65
|
+
homepage: http://github.com/xianhuazhou
|
66
|
+
licenses: []
|
67
|
+
|
68
|
+
post_install_message:
|
69
|
+
rdoc_options: []
|
70
|
+
|
71
|
+
require_paths:
|
72
|
+
- lib
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
segments:
|
79
|
+
- 0
|
80
|
+
version: "0"
|
81
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
segments:
|
87
|
+
- 0
|
88
|
+
version: "0"
|
89
|
+
requirements: []
|
90
|
+
|
91
|
+
rubyforge_project: mysql2mysql
|
92
|
+
rubygems_version: 1.3.7
|
93
|
+
signing_key:
|
94
|
+
specification_version: 3
|
95
|
+
summary: Dump table's structure and data between mysql servers and databases.
|
96
|
+
test_files: []
|
97
|
+
|