mysql2mysql 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Changelog +4 -1
- data/INSTALL +1 -1
- data/README.rdoc +127 -5
- data/lib/mysql_2_mysql.rb +137 -76
- data/spec/mysql_2_mysql_spec.rb +402 -0
- metadata +4 -3
data/Changelog
CHANGED
data/INSTALL
CHANGED
data/README.rdoc
CHANGED
@@ -1,19 +1,141 @@
|
|
1
1
|
== Description
|
2
2
|
|
3
|
-
|
3
|
+
Dump table's structure and data between MySql servers and databases.
|
4
|
+
|
5
|
+
== INSTALL
|
6
|
+
|
7
|
+
$ [sudo] gem install mysql2mysql
|
8
|
+
|
9
|
+
== Requirements
|
10
|
+
|
11
|
+
Sequel with the mysql driver.
|
12
|
+
|
13
|
+
== Examples
|
4
14
|
|
5
|
-
== Example
|
6
15
|
require 'rubygems'
|
7
16
|
require 'mysql2mysql'
|
8
17
|
Mysql2Mysql.new.
|
9
18
|
from('mysql://mysql.server1/test?user=root&password=pass').
|
10
19
|
to('mysql://mysql.server2/test?user=root&password=pass').
|
11
|
-
tables('
|
12
|
-
dump(:charset => 'utf8'
|
20
|
+
tables('dbname').
|
21
|
+
dump(:charset => 'utf8')
|
22
|
+
|
23
|
+
== Methods
|
24
|
+
|
25
|
+
from: parameters for connect to the source MySql server, it's used for Sequel.connect
|
26
|
+
to: parameters for connect to the target MySql server, it's used for Sequel.connect
|
27
|
+
tables: databases/tables need to dump
|
28
|
+
exclude: databases/tables need to exclude
|
29
|
+
dump: do the clone databases/tables action with some options
|
30
|
+
|
31
|
+
== Connection
|
32
|
+
|
33
|
+
The "from" and "to" methods provide a DSN String or Hash for the database connection.
|
34
|
+
Mysql2Mysql.new.from('mysql://localhost/test?user=root&password=pass')
|
35
|
+
It's same as below
|
36
|
+
Mysql2Mysql.new.from(:host => 'localhost', :user => 'root', :password => 'pass)
|
37
|
+
|
38
|
+
BTW: if the source and target server in the same manchine, please don't use the "localhost" for 2 connections,
|
39
|
+
but it works if one connection use "localhost", another use "127.0.0.1".
|
40
|
+
|
41
|
+
== tables && exclude
|
42
|
+
|
43
|
+
Both methods are very similar, but the "exclude" method will take some tables out from the "tables" method. So,
|
44
|
+
finall_tables_to_dump = tables - exclude
|
45
|
+
|
46
|
+
String, Hash, Array are supported. e.g.
|
47
|
+
|
48
|
+
# all databases/tables
|
49
|
+
tables('*')
|
50
|
+
|
51
|
+
# all tables in the database "dbname"
|
52
|
+
tables('dbname')
|
53
|
+
|
54
|
+
# same as above
|
55
|
+
tables(:dbname)
|
56
|
+
|
57
|
+
# all tables in the database "blog" and "log"
|
58
|
+
tables(['blog', 'log'])
|
59
|
+
|
60
|
+
# same as above
|
61
|
+
tables({'blog' => '*', 'log' => '*'})
|
62
|
+
|
63
|
+
# tables "posts" and "comments" in the database "blog" and
|
64
|
+
# all tables in the database "log"
|
65
|
+
tables({'blog' => ['posts', 'comments'], 'log' => '*'})
|
66
|
+
|
67
|
+
# Regular expression supported
|
68
|
+
|
69
|
+
# all tables with prefix 'wp_' in the database 'blog'
|
70
|
+
tables({:blog => /^(wp)_/})
|
71
|
+
|
72
|
+
# all tables with prefix 'wp_' in the database 'blog' and
|
73
|
+
# all tables only contains 26 lowercase letters in the database "log"
|
74
|
+
tables({:blog => /^(wp)_/, :log => /^[a-z]+$/})
|
75
|
+
|
76
|
+
# all databaes with prefix "www_"
|
77
|
+
tables({/^(www)_/ => '*')
|
78
|
+
|
79
|
+
# exclude
|
80
|
+
|
81
|
+
# only the 'log' database's tables will be cloned
|
82
|
+
tables('blog', 'log').exclude('blog')
|
83
|
+
|
84
|
+
# all databases/tables exclude the database "mysql", "test" and "information_schema"
|
85
|
+
tables('*').exclude(['mysql', 'test', 'information_schema'])
|
86
|
+
|
87
|
+
# all tables in the database "blog" exclude the "comments" table
|
88
|
+
tables('blog').exclude({'blog' => 'comments'})
|
89
|
+
|
90
|
+
== dump
|
91
|
+
|
92
|
+
The "dump" method is expect a Hash as it's parameter, defualt is {}. options:
|
93
|
+
|
94
|
+
:charset => "utf8", "latin" or any other valid charsets, default is nil
|
95
|
+
:with_data => Dump data if it's true, otherwise only tables' structure will be dumped, default is true
|
96
|
+
:drop_table_first => Drop the table first if the table is already exists, default is true
|
97
|
+
:rows_per_select => Number of rows pre select from the source server for the import, default is 1000
|
98
|
+
:before_all => Proc style call back, it can be executed before the dump action started, default is nil
|
99
|
+
:after_all => Proc style call back, it can be executed after the dump action finished, default is nil
|
100
|
+
:before_each => Proc style call back, it can be executed before each table dump started, default is nil
|
101
|
+
:aftere_each => Proc style call back, it can be executed after each table dump finished,, default is nil
|
102
|
+
|
103
|
+
== Cases
|
104
|
+
|
105
|
+
clone 2 databases:
|
106
|
+
|
107
|
+
Mysql2Mysql.new.
|
108
|
+
from('mysql://localhost:3306/test?user=root&password=').
|
109
|
+
to('mysql://127.0.0.1:3307/test?user=root&password=').
|
110
|
+
tables(['db1', 'db2']).
|
111
|
+
dump
|
112
|
+
|
113
|
+
clone all databases exclude "mysql", "test" and "information_schema":
|
114
|
+
Mysql2Mysql.new.
|
115
|
+
from('mysql://localhost:3306/test?user=root&password=').
|
116
|
+
to('mysql://127.0.0.1:3307/test?user=root&password=').
|
117
|
+
tables(:all).
|
118
|
+
exclude([:mysql, :test, :information_schema]).
|
119
|
+
dump(:charset => 'utf8')
|
120
|
+
|
121
|
+
clone all tables in the database "blog" and rename it to "blog2" in the same server:
|
122
|
+
Mysql2Mysql.new.
|
123
|
+
from('mysql://localhost:3306/test?user=root&password=').
|
124
|
+
to('mysql://localhost:3306/test?user=root&password=').
|
125
|
+
tables('blog').
|
126
|
+
dump(:charset => 'utf8', :before_each => lambda {|db, tb|
|
127
|
+
# change the target database name here from "blog" to "blog2"
|
128
|
+
# the table name can be changed as well, but we keep the table name here
|
129
|
+
return "blog2", tb
|
130
|
+
})
|
13
131
|
|
14
132
|
== Version
|
15
133
|
|
16
|
-
v0.0.
|
134
|
+
v0.0.2
|
135
|
+
|
136
|
+
== TODO
|
137
|
+
|
138
|
+
more docs and specs
|
17
139
|
|
18
140
|
== Author
|
19
141
|
|
data/lib/mysql_2_mysql.rb
CHANGED
@@ -9,7 +9,7 @@
|
|
9
9
|
#
|
10
10
|
# == Version
|
11
11
|
#
|
12
|
-
# v0.0.
|
12
|
+
# v0.0.2
|
13
13
|
#
|
14
14
|
# == Author
|
15
15
|
#
|
@@ -19,7 +19,7 @@ require 'sequel'
|
|
19
19
|
|
20
20
|
class Mysql2Mysql
|
21
21
|
|
22
|
-
VERSION = '0.0.
|
22
|
+
VERSION = '0.0.2'
|
23
23
|
|
24
24
|
def self.version
|
25
25
|
VERSION
|
@@ -68,12 +68,15 @@ class Mysql2Mysql
|
|
68
68
|
if opts[:after_each].respond_to? :call
|
69
69
|
opts[:after_each].call(database, table)
|
70
70
|
end
|
71
|
+
|
71
72
|
end
|
72
73
|
end
|
73
74
|
|
74
75
|
# after all callback
|
75
76
|
after_dump opts
|
76
77
|
|
78
|
+
# disconnect
|
79
|
+
free_db_connection
|
77
80
|
end
|
78
81
|
|
79
82
|
private
|
@@ -81,13 +84,16 @@ class Mysql2Mysql
|
|
81
84
|
def dump_opts(opts)
|
82
85
|
{
|
83
86
|
# it's used for "SET NAMES #{charset}"
|
84
|
-
:charset =>
|
87
|
+
:charset => nil,
|
85
88
|
|
86
89
|
# dump data or just table structure
|
87
90
|
:with_data => true,
|
88
91
|
|
89
92
|
# drop the table before do dump the table
|
90
|
-
:drop_table_first =>
|
93
|
+
:drop_table_first => true,
|
94
|
+
|
95
|
+
# number of rows per select
|
96
|
+
:rows_per_select => 1000,
|
91
97
|
|
92
98
|
# callbacks
|
93
99
|
:before_all => nil,
|
@@ -99,14 +105,21 @@ class Mysql2Mysql
|
|
99
105
|
|
100
106
|
def before_dump(opts)
|
101
107
|
# prepare dump
|
102
|
-
[
|
103
|
-
"SET NAMES #{opts[:charset]}",
|
108
|
+
sqls = [
|
104
109
|
"SET FOREIGN_KEY_CHECKS = 0",
|
105
110
|
"SET UNIQUE_CHECKS = 0"
|
106
|
-
]
|
111
|
+
]
|
112
|
+
|
113
|
+
if opts[:charset]
|
114
|
+
sql = "SET NAMES #{opts[:charset]}"
|
115
|
+
run_sql sql, :on_connection => @from_db
|
116
|
+
sqls << sql
|
117
|
+
end
|
118
|
+
|
119
|
+
sqls.each do |sql|
|
107
120
|
run_sql sql, :on_connection => @to_db
|
108
121
|
end
|
109
|
-
opts[:before_all].call(@from_db, @to_db) if opts[:
|
122
|
+
opts[:before_all].call(@from_db, @to_db) if opts[:before_all].respond_to? :call
|
110
123
|
end
|
111
124
|
|
112
125
|
def after_dump(opts)
|
@@ -118,13 +131,22 @@ class Mysql2Mysql
|
|
118
131
|
run_sql sql, :on_connection => @to_db
|
119
132
|
end
|
120
133
|
opts[:after_all].call(@from_db, @to_db) if opts[:after_all].respond_to? :call
|
134
|
+
|
135
|
+
@from_db.disconnect
|
136
|
+
@to_db.disconnect
|
121
137
|
end
|
122
138
|
|
123
139
|
def dump_table(from_database, from_table, to_database, to_table, opts = {})
|
124
140
|
use_db from_database, :on_connection => @from_db
|
125
|
-
create_table_ddl = @from_db.fetch("SHOW CREATE TABLE #{from_table}").first[:'Create Table']
|
126
141
|
|
127
|
-
|
142
|
+
create_database to_database
|
143
|
+
|
144
|
+
create_table from_table, to_table, opts
|
145
|
+
|
146
|
+
dump_table_data @from_db[from_table.to_sym], @to_db[to_table.to_sym], opts
|
147
|
+
end
|
148
|
+
|
149
|
+
def create_database(to_database)
|
128
150
|
begin
|
129
151
|
use_db to_database, :on_connection => @to_db
|
130
152
|
rescue Sequel::DatabaseError => e
|
@@ -135,21 +157,46 @@ class Mysql2Mysql
|
|
135
157
|
raise Mysql2MysqlException.new "create database #{to_database} failed\n DSN info: #{@to_db}"
|
136
158
|
end
|
137
159
|
end
|
160
|
+
to_database
|
161
|
+
end
|
138
162
|
|
139
|
-
|
140
|
-
|
163
|
+
def create_table(from_table, to_table, opts)
|
164
|
+
create_table_ddl = @from_db.fetch("SHOW CREATE TABLE #{from_table}").
|
165
|
+
first[:'Create Table'].
|
166
|
+
gsub("`#{from_table}`", "`#{to_table}`")
|
167
|
+
|
168
|
+
run_sql "DROP TABLE IF EXISTS #{to_table}", :on_connection => @to_db if opts[:drop_table_first]
|
169
|
+
if opts[:drop_table_first] or not @to_db.table_exists?(to_table.to_sym)
|
141
170
|
begin
|
142
|
-
run_sql create_table_ddl
|
171
|
+
run_sql create_table_ddl, :on_connection => @to_db
|
143
172
|
rescue Exception => e
|
144
173
|
raise Mysql2MysqlException.new "create table #{to_table} failed in the database #{to_database}\n DSN info: #{@to_db}\n: message: #{e}"
|
145
174
|
end
|
146
175
|
end
|
176
|
+
end
|
147
177
|
|
178
|
+
def dump_table_data(from_db_table, to_db_table, opts)
|
148
179
|
return unless opts[:with_data]
|
149
180
|
|
150
|
-
|
151
|
-
|
152
|
-
|
181
|
+
total = from_db_table.count
|
182
|
+
return if total == 0
|
183
|
+
|
184
|
+
limit = opts[:rows_per_select]
|
185
|
+
limit = total if limit >= total
|
186
|
+
|
187
|
+
columns = from_db_table.columns
|
188
|
+
|
189
|
+
# temp variables
|
190
|
+
rows = []
|
191
|
+
offset = 0
|
192
|
+
row = {}
|
193
|
+
|
194
|
+
0.step(total - 1, limit) do |offset|
|
195
|
+
rows = from_db_table.limit(limit, offset).collect do |row|
|
196
|
+
row.values
|
197
|
+
end
|
198
|
+
|
199
|
+
to_db_table.import columns, rows
|
153
200
|
end
|
154
201
|
end
|
155
202
|
|
@@ -162,8 +209,13 @@ class Mysql2Mysql
|
|
162
209
|
end
|
163
210
|
|
164
211
|
def init_db_connection
|
165
|
-
@from_db = db_connection
|
166
|
-
@to_db = db_connection
|
212
|
+
@from_db = db_connection(@from)
|
213
|
+
@to_db = db_connection(@to)
|
214
|
+
end
|
215
|
+
|
216
|
+
def free_db_connection
|
217
|
+
@from_db.disconnect if @from_db
|
218
|
+
@to_db.disconnect if @to_db
|
167
219
|
end
|
168
220
|
|
169
221
|
def db_connection(dsn)
|
@@ -173,7 +225,7 @@ class Mysql2Mysql
|
|
173
225
|
|
174
226
|
def tables_list
|
175
227
|
raise Mysql2MysqlException.new 'No tables need to dump' if @tables.nil?
|
176
|
-
|
228
|
+
filter all_valid_tables
|
177
229
|
end
|
178
230
|
|
179
231
|
def all_databases
|
@@ -182,45 +234,45 @@ class Mysql2Mysql
|
|
182
234
|
end
|
183
235
|
end
|
184
236
|
|
185
|
-
def
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
237
|
+
def all_valid_tables
|
238
|
+
orig_tables = convert_tables @tables
|
239
|
+
|
240
|
+
if is_all? orig_tables
|
241
|
+
return all_databases.inject({}) do |all_tables, dbname|
|
242
|
+
all_tables.merge dbname => get_tables_by_db(dbname)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
all_tables = {}
|
247
|
+
|
248
|
+
all_databases.each do |dbname|
|
249
|
+
orig_tables.each do |orig_dbname, orig_tbname|
|
250
|
+
next unless is_eql_or_match?(orig_dbname, dbname)
|
251
|
+
|
252
|
+
tables = get_tables_by_db dbname
|
253
|
+
|
254
|
+
if is_all? orig_tbname
|
255
|
+
all_tables[dbname] = tables
|
256
|
+
break
|
190
257
|
end
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
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
|
258
|
+
|
259
|
+
orig_tbnames = [orig_tbname] unless orig_tbname.is_a? Array
|
260
|
+
tables = tables.find_all do |tbname|
|
261
|
+
orig_tbnames.find do |orig_tbname|
|
262
|
+
is_eql_or_match?(orig_tbname, tbname)
|
212
263
|
end
|
213
264
|
end
|
214
265
|
|
215
|
-
all_tables
|
266
|
+
all_tables[dbname] = tables
|
216
267
|
end
|
268
|
+
end
|
217
269
|
|
218
|
-
|
270
|
+
all_tables
|
219
271
|
end
|
220
272
|
|
221
273
|
def is_eql_or_match?(origin, current)
|
222
274
|
if origin.is_a? Regexp
|
223
|
-
origin.match current
|
275
|
+
origin.match current.to_s
|
224
276
|
else
|
225
277
|
origin.to_s == current.to_s
|
226
278
|
end
|
@@ -236,42 +288,51 @@ class Mysql2Mysql
|
|
236
288
|
end
|
237
289
|
|
238
290
|
def filter(origin_tables)
|
239
|
-
need_exclude = lambda do |origin, exclude|
|
240
|
-
is_eql_or_match? origin.to_s, exclude
|
241
|
-
end
|
242
|
-
|
243
291
|
return origin_tables if @exclude.nil?
|
244
292
|
|
245
|
-
exclude_tables =
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
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
|
293
|
+
exclude_tables = convert_tables @exclude
|
294
|
+
|
295
|
+
reject_table = lambda do |dbname, tbname|
|
296
|
+
exclude_tables.each do |exclude_dbname, exclude_tbnames|
|
297
|
+
next unless is_eql_or_match?(exclude_dbname, dbname)
|
298
|
+
|
299
|
+
return true if is_all?(exclude_tbnames)
|
300
|
+
|
301
|
+
exclude_tbnames = [exclude_tbnames] unless exclude_tbnames.is_a? Array
|
302
|
+
return true if exclude_tbnames.find {|exclude_tbname|
|
303
|
+
is_eql_or_match? exclude_tbname, tbname
|
304
|
+
}
|
265
305
|
end
|
266
306
|
|
267
307
|
false
|
268
308
|
end
|
269
309
|
|
270
|
-
origin_tables.
|
271
|
-
|
310
|
+
origin_tables.each do |dbname, tbnames|
|
311
|
+
origin_tables[dbname] = tbnames.find_all do |tbname|
|
312
|
+
not reject_table.call(dbname, tbname)
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
def convert_tables(tables)
|
318
|
+
case tables
|
319
|
+
when Symbol, String
|
320
|
+
if is_all? tables
|
321
|
+
tables
|
322
|
+
else
|
323
|
+
{tables.to_s => '*'}
|
324
|
+
end
|
325
|
+
when Array
|
326
|
+
tables.inject({}) do |items, it|
|
327
|
+
items.merge it.to_s => '*'
|
328
|
+
end
|
329
|
+
when Hash
|
330
|
+
tables
|
331
|
+
else
|
332
|
+
raise Mysql2MysqlException.new 'Invalid "tables" or "exclude" parameters given'
|
272
333
|
end
|
273
334
|
end
|
274
|
-
end
|
275
335
|
|
276
|
-
class Mysql2MysqlException < Exception
|
277
336
|
end
|
337
|
+
|
338
|
+
class Mysql2MysqlException < Exception; end
|
@@ -0,0 +1,402 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../lib/mysql_2_mysql.rb'
|
2
|
+
|
3
|
+
require 'sequel'
|
4
|
+
$from_dsn = 'mysql://localhost:3306/test?user=root&password='
|
5
|
+
$to_dsn = 'mysql://127.0.0.1:3307/test?user=root&password='
|
6
|
+
|
7
|
+
def disconnect(m2m)
|
8
|
+
m2m.instance_variable_get('@from_db').disconnect
|
9
|
+
m2m.instance_variable_get('@to_db').disconnect
|
10
|
+
end
|
11
|
+
|
12
|
+
describe Mysql2Mysql, 'initialize' do
|
13
|
+
it "can initialize some parameters" do
|
14
|
+
|
15
|
+
m2m = Mysql2Mysql.new :from => $from_dsn, :to => $to_dsn, :tables => {:test => '*'}, :exclude => {}
|
16
|
+
m2m.instance_variable_get('@from').should == $from_dsn
|
17
|
+
m2m.instance_variable_get('@to').should == $to_dsn
|
18
|
+
m2m.instance_variable_get('@tables').should == {:test => '*'}
|
19
|
+
m2m.instance_variable_get('@exclude').should == {}
|
20
|
+
|
21
|
+
m2m.from($from_dsn + '1').instance_variable_get('@from').should == $from_dsn + '1'
|
22
|
+
m2m.to($to_dsn + '1').instance_variable_get('@to').should == $to_dsn + '1'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe Mysql2Mysql, 'dump_opts' do
|
27
|
+
it "should have some pre-defined options" do
|
28
|
+
m2m = Mysql2Mysql.new
|
29
|
+
m2m.send(:dump_opts, {}).should == {
|
30
|
+
:charset => nil,
|
31
|
+
:with_data => true,
|
32
|
+
:drop_table_first => true,
|
33
|
+
:rows_per_select => 1000,
|
34
|
+
:before_all => nil,
|
35
|
+
:after_all => nil,
|
36
|
+
:before_each => nil,
|
37
|
+
:after_each => nil
|
38
|
+
}
|
39
|
+
|
40
|
+
m2m.send(:dump_opts, {:charset => 'utf8'})[:charset].should == 'utf8'
|
41
|
+
m2m.send(:dump_opts, {:before_all => lambda{}})[:before_all].should == lambda{}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe Mysql2Mysql, 'before_dump' do
|
46
|
+
it "can change some mysql variables" do
|
47
|
+
m2m = Mysql2Mysql.new :from => $from_dsn, :to => $to_dsn
|
48
|
+
m2m.send :init_db_connection
|
49
|
+
m2m.send(:before_dump, m2m.send(:dump_opts, {:charset => 'utf8'}))
|
50
|
+
to_db = m2m.instance_variable_get('@to_db')
|
51
|
+
|
52
|
+
to_db.fetch("SHOW VARIABLES LIKE 'FOREIGN_KEY_CHECKS'").first[:Value].should == 'OFF'
|
53
|
+
to_db.fetch("SHOW VARIABLES LIKE 'UNIQUE_CHECKS'").first[:Value].should == 'OFF'
|
54
|
+
to_db.fetch("SHOW VARIABLES LIKE 'CHARACTER_SET_CLIENT'").first[:Value].should == 'utf8'
|
55
|
+
to_db.fetch("SHOW VARIABLES LIKE 'CHARACTER_SET_CONNECTION'").first[:Value].should == 'utf8'
|
56
|
+
|
57
|
+
disconnect(m2m)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "can execute the code hook 'before_all'" do
|
61
|
+
m2m = Mysql2Mysql.new :from => $from_dsn, :to => $to_dsn
|
62
|
+
m2m.send :init_db_connection
|
63
|
+
$mysql_expr = nil
|
64
|
+
m2m.send(:before_dump, m2m.send(:dump_opts, {:before_all => lambda{|from_db, to_db|
|
65
|
+
$mysql_expr = from_db.fetch("SELECT 1+1 AS num").first[:num]
|
66
|
+
}}))
|
67
|
+
$mysql_expr.should == 2
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe Mysql2Mysql, 'after_dump' do
|
72
|
+
it "can revert some mysql variable" do
|
73
|
+
m2m = Mysql2Mysql.new :from => $from_dsn, :to => $to_dsn
|
74
|
+
m2m.send :init_db_connection
|
75
|
+
m2m.send(:after_dump, m2m.send(:dump_opts, {}))
|
76
|
+
to_db = m2m.instance_variable_get('@to_db')
|
77
|
+
|
78
|
+
to_db.fetch("SHOW VARIABLES LIKE 'FOREIGN_KEY_CHECKS'").first[:Value].should == 'ON'
|
79
|
+
to_db.fetch("SHOW VARIABLES LIKE 'UNIQUE_CHECKS'").first[:Value].should == 'ON'
|
80
|
+
|
81
|
+
disconnect(m2m)
|
82
|
+
end
|
83
|
+
|
84
|
+
it "can execute the code hook 'after_all'" do
|
85
|
+
m2m = Mysql2Mysql.new :from => $from_dsn, :to => $to_dsn
|
86
|
+
m2m.send :init_db_connection
|
87
|
+
$mysql_expr = nil
|
88
|
+
m2m.send(:after_dump, m2m.send(:dump_opts, {:after_all => lambda{|from_db, to_db|
|
89
|
+
$mysql_expr = from_db.fetch("SELECT 1+1 AS num").first[:num]
|
90
|
+
}}))
|
91
|
+
$mysql_expr.should == 2
|
92
|
+
disconnect(m2m)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe Mysql2Mysql, 'create_database' do
|
97
|
+
it "can create a new database on the target mysql server" do
|
98
|
+
db_name = 'db_m2m'
|
99
|
+
m2m = Mysql2Mysql.new :from => $from_dsn, :to => $to_dsn
|
100
|
+
m2m.send :init_db_connection
|
101
|
+
|
102
|
+
to_db = m2m.instance_variable_get('@to_db')
|
103
|
+
to_db.run "DROP DATABASE IF EXISTS #{db_name}"
|
104
|
+
|
105
|
+
m2m.send :create_database, db_name
|
106
|
+
to_db.fetch("SHOW DATABASES").all.collect{|row|row.values.first}.should include(db_name)
|
107
|
+
|
108
|
+
# do it again
|
109
|
+
m2m.send :create_database, db_name
|
110
|
+
to_db.fetch("SHOW DATABASES").all.collect{|row|row.values.first}.should include(db_name)
|
111
|
+
|
112
|
+
# clean up
|
113
|
+
to_db.run "DROP DATABASE #{db_name}"
|
114
|
+
disconnect(m2m)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe Mysql2Mysql, 'create_table' do
|
119
|
+
it "can create a new table on the target mysql server according to the source mysql server" do
|
120
|
+
db_name = 'db_m2m'
|
121
|
+
m2m = Mysql2Mysql.new :from => $from_dsn, :to => $to_dsn
|
122
|
+
m2m.send :init_db_connection
|
123
|
+
|
124
|
+
from_db = m2m.instance_variable_get('@from_db')
|
125
|
+
to_db = m2m.instance_variable_get('@to_db')
|
126
|
+
|
127
|
+
from_db.run "DROP DATABASE IF EXISTS #{db_name}"
|
128
|
+
to_db.run "DROP DATABASE IF EXISTS #{db_name}"
|
129
|
+
|
130
|
+
from_db.run "CREATE DATABASE #{db_name}"
|
131
|
+
from_db.run "USE #{db_name}"
|
132
|
+
from_db.run "CREATE TABLE t(id INT)"
|
133
|
+
|
134
|
+
# use the same table name
|
135
|
+
m2m.send :create_database, db_name
|
136
|
+
m2m.send :create_table, 't', 't', m2m.send(:dump_opts, {})
|
137
|
+
to_db.tables.should include(:t)
|
138
|
+
|
139
|
+
# use different table name
|
140
|
+
m2m.send :create_table, 't', 't2', m2m.send(:dump_opts, {})
|
141
|
+
to_db.tables.should include(:t2)
|
142
|
+
|
143
|
+
from_db.run "DROP DATABASE #{db_name}"
|
144
|
+
to_db.run "DROP DATABASE #{db_name}"
|
145
|
+
|
146
|
+
disconnect(m2m)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
describe Mysql2Mysql, 'dump_table_data' do
|
151
|
+
it "can copy table's data from one server to another server" do
|
152
|
+
db_name = 'db_m2m'
|
153
|
+
m2m = Mysql2Mysql.new :from => $from_dsn, :to => $to_dsn
|
154
|
+
m2m.send :init_db_connection
|
155
|
+
|
156
|
+
from_db = m2m.instance_variable_get('@from_db')
|
157
|
+
to_db = m2m.instance_variable_get('@to_db')
|
158
|
+
|
159
|
+
from_db.run "DROP DATABASE IF EXISTS #{db_name}"
|
160
|
+
to_db.run "DROP DATABASE IF EXISTS #{db_name}"
|
161
|
+
|
162
|
+
from_db.run "CREATE DATABASE #{db_name}"
|
163
|
+
from_db.run "USE #{db_name}"
|
164
|
+
from_db.run "CREATE TABLE t(id INT)"
|
165
|
+
|
166
|
+
m2m.send :create_database, db_name
|
167
|
+
m2m.send :create_table, 't', 't', m2m.send(:dump_opts, {})
|
168
|
+
|
169
|
+
# 1 row
|
170
|
+
from_db.run "INSERT INTO t(id) VALUES(1)"
|
171
|
+
m2m.send :dump_table_data, from_db[:t], to_db[:t], m2m.send(:dump_opts, {})
|
172
|
+
to_db[:t].first.should == {:id => 1}
|
173
|
+
|
174
|
+
# 2 rows now
|
175
|
+
from_db.run "INSERT INTO t(id) VALUES(2)"
|
176
|
+
m2m.send :dump_table_data, from_db[:t], to_db[:t], m2m.send(:dump_opts, {})
|
177
|
+
to_db[:t].all == [{:id => 1}, {:id => 2}]
|
178
|
+
|
179
|
+
# 2010 rows
|
180
|
+
from_db.run "TRUNCATE TABLE t"
|
181
|
+
to_db.run "TRUNCATE TABLE t"
|
182
|
+
1.upto(2010) do |i|
|
183
|
+
from_db[:t].insert :id => i
|
184
|
+
end
|
185
|
+
m2m.send :dump_table_data, from_db[:t], to_db[:t], m2m.send(:dump_opts, {})
|
186
|
+
to_db[:t].count.should == 2010
|
187
|
+
|
188
|
+
from_db.run "DROP DATABASE #{db_name}"
|
189
|
+
to_db.run "DROP DATABASE #{db_name}"
|
190
|
+
|
191
|
+
disconnect(m2m)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
describe Mysql2Mysql, 'convert_tables' do
|
196
|
+
it "should support several different types" do
|
197
|
+
m2m = Mysql2Mysql.new
|
198
|
+
m2m.send(:convert_tables, '*').should == '*'
|
199
|
+
m2m.send(:convert_tables, :all).should == :all
|
200
|
+
m2m.send(:convert_tables, 'db_m2m' => '*').should == {'db_m2m' => '*'}
|
201
|
+
m2m.send(:convert_tables, 'db_m2m').should == {'db_m2m' => '*'}
|
202
|
+
m2m.send(:convert_tables, :db_m2m).should == {'db_m2m' => '*'}
|
203
|
+
m2m.send(:convert_tables, [:db1, :db2]).should == {'db1' => '*', 'db2' => '*'}
|
204
|
+
m2m.send(:convert_tables, {:db1 => ['tb1', 'tb2'], :db2 => '*'}).should == {:db1 => ['tb1', 'tb2'], :db2 => '*'}
|
205
|
+
lambda{m2m.send(:convert_tables, lambda{})}.should raise_error(Mysql2MysqlException)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
describe Mysql2Mysql, 'is_eql_or_match?' do
|
210
|
+
it "should support regular expression, symbol and string compare" do
|
211
|
+
m2m = Mysql2Mysql.new
|
212
|
+
m2m.send(:is_eql_or_match?, 'abc', 'abc').should be_true
|
213
|
+
m2m.send(:is_eql_or_match?, :abc, 'abc').should be_true
|
214
|
+
m2m.send(:is_eql_or_match?, 'abc', :abc).should be_true
|
215
|
+
m2m.send(:is_eql_or_match?, 10, "10").should be_true
|
216
|
+
m2m.send(:is_eql_or_match?, /^[0-9]$/, 9).should be_true
|
217
|
+
m2m.send(:is_eql_or_match?, /^[0-9]$/, 0).should be_true
|
218
|
+
m2m.send(:is_eql_or_match?, /^[0-9]$/, "3").should be_true
|
219
|
+
m2m.send(:is_eql_or_match?, /^[0-9]$/, 10).should be_false
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
describe Mysql2Mysql, 'is_all?' do
|
224
|
+
it "should support '*' and ':all'" do
|
225
|
+
m2m = Mysql2Mysql.new
|
226
|
+
m2m.send(:is_all?, '*').should be_true
|
227
|
+
m2m.send(:is_all?, :all).should be_true
|
228
|
+
m2m.send(:is_all?, 'all').should be_false
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
describe Mysql2Mysql do
|
233
|
+
it "can clone one database" do
|
234
|
+
db_name = 'db_m2m'
|
235
|
+
|
236
|
+
sequel = Sequel.connect $from_dsn
|
237
|
+
sequel.run "CREATE DATABASE #{db_name}"
|
238
|
+
sequel.run "USE #{db_name}"
|
239
|
+
sequel.run "CREATE TABLE t(id INT)"
|
240
|
+
|
241
|
+
m2m = Mysql2Mysql.new :from => $from_dsn, :to => $to_dsn, :tables => {db_name => '*'}
|
242
|
+
m2m.dump
|
243
|
+
from_db = m2m.instance_variable_get('@from_db')
|
244
|
+
to_db = m2m.instance_variable_get('@to_db')
|
245
|
+
to_db.fetch("SHOW DATABASES").all.collect{|row|row.values.first}.should include(db_name)
|
246
|
+
|
247
|
+
from_db.run "DROP DATABASE #{db_name}"
|
248
|
+
to_db.run "DROP DATABASE #{db_name}"
|
249
|
+
sequel.disconnect
|
250
|
+
disconnect(m2m)
|
251
|
+
end
|
252
|
+
|
253
|
+
it "can clone two databases" do
|
254
|
+
db1_name = 'db_m2m_a'
|
255
|
+
db2_name = 'db_m2m_b'
|
256
|
+
|
257
|
+
sequel = Sequel.connect $from_dsn
|
258
|
+
sequel.run "DROP DATABASE IF EXISTS #{db1_name}"
|
259
|
+
sequel.run "DROP DATABASE IF EXISTS #{db2_name}"
|
260
|
+
sequel.run "CREATE DATABASE #{db1_name}"
|
261
|
+
sequel.run "CREATE DATABASE #{db2_name}"
|
262
|
+
sequel.run "USE #{db1_name}"
|
263
|
+
sequel.run "CREATE TABLE t1(id INT)"
|
264
|
+
sequel.run "USE #{db2_name}"
|
265
|
+
sequel.run "CREATE TABLE t2(id INT)"
|
266
|
+
|
267
|
+
m2m = Mysql2Mysql.new :from => $from_dsn, :to => $to_dsn, :tables => [db1_name, db2_name]
|
268
|
+
m2m.dump
|
269
|
+
from_db = m2m.instance_variable_get('@from_db')
|
270
|
+
to_db = m2m.instance_variable_get('@to_db')
|
271
|
+
to_db.fetch("SHOW DATABASES").all.collect{|row|row.values.first}.should include(db1_name)
|
272
|
+
to_db.fetch("SHOW DATABASES").all.collect{|row|row.values.first}.should include(db2_name)
|
273
|
+
to_db.run "use #{db1_name}"
|
274
|
+
to_db.tables.should include(:t1)
|
275
|
+
to_db.run "use #{db2_name}"
|
276
|
+
to_db.tables.should include(:t2)
|
277
|
+
|
278
|
+
from_db.run "DROP DATABASE #{db1_name}"
|
279
|
+
from_db.run "DROP DATABASE #{db2_name}"
|
280
|
+
to_db.run "DROP DATABASE #{db1_name}"
|
281
|
+
to_db.run "DROP DATABASE #{db2_name}"
|
282
|
+
sequel.disconnect
|
283
|
+
disconnect(m2m)
|
284
|
+
end
|
285
|
+
|
286
|
+
it "can clone 1 table from 2 tables" do
|
287
|
+
db_name = 'db_m2m_a'
|
288
|
+
sequel = Sequel.connect $from_dsn
|
289
|
+
sequel.run "DROP DATABASE IF EXISTS #{db_name}"
|
290
|
+
sequel.run "CREATE DATABASE #{db_name}"
|
291
|
+
sequel.run "USE #{db_name}"
|
292
|
+
sequel.run "CREATE TABLE t1(id INT)"
|
293
|
+
sequel.run "CREATE TABLE t2(id INT)"
|
294
|
+
|
295
|
+
m2m = Mysql2Mysql.new :from => $from_dsn, :to => $to_dsn, :tables => {db_name => 't1'}
|
296
|
+
m2m.dump
|
297
|
+
from_db = m2m.instance_variable_get('@from_db')
|
298
|
+
to_db = m2m.instance_variable_get('@to_db')
|
299
|
+
|
300
|
+
to_db.fetch("SHOW DATABASES").all.collect{|row|row.values.first}.should include(db_name)
|
301
|
+
to_db.run "USE #{db_name}"
|
302
|
+
to_db.tables.should == [:t1]
|
303
|
+
|
304
|
+
to_db.run "DROP DATABASE #{db_name}"
|
305
|
+
disconnect(m2m)
|
306
|
+
|
307
|
+
m2m = Mysql2Mysql.new :from => $from_dsn, :to => $to_dsn, :tables => {db_name => 't2'}
|
308
|
+
m2m.dump
|
309
|
+
from_db = m2m.instance_variable_get('@from_db')
|
310
|
+
to_db = m2m.instance_variable_get('@to_db')
|
311
|
+
to_db.run "USE #{db_name}"
|
312
|
+
to_db.tables.should == [:t2]
|
313
|
+
|
314
|
+
from_db.run "DROP DATABASE #{db_name}"
|
315
|
+
to_db.run "DROP DATABASE #{db_name}"
|
316
|
+
sequel.disconnect
|
317
|
+
disconnect(m2m)
|
318
|
+
end
|
319
|
+
|
320
|
+
it "should support regular expression" do
|
321
|
+
db1_name = "db_m2m_a"
|
322
|
+
db2_name = "db_m2m_b"
|
323
|
+
db3_name = "db_2m2"
|
324
|
+
|
325
|
+
sequel = Sequel.connect $from_dsn
|
326
|
+
sequel.run "DROP DATABASE IF EXISTS #{db1_name}"
|
327
|
+
sequel.run "DROP DATABASE IF EXISTS #{db2_name}"
|
328
|
+
sequel.run "DROP DATABASE IF EXISTS #{db3_name}"
|
329
|
+
sequel.run "CREATE DATABASE #{db1_name}"
|
330
|
+
sequel.run "CREATE DATABASE #{db2_name}"
|
331
|
+
sequel.run "CREATE DATABASE #{db3_name}"
|
332
|
+
sequel.run "USE #{db1_name}"
|
333
|
+
sequel.run "CREATE TABLE t1(id INT)"
|
334
|
+
sequel.run "CREATE TABLE t2(id INT)"
|
335
|
+
sequel.run "CREATE TABLE t10(id INT)"
|
336
|
+
sequel.run "USE #{db2_name}"
|
337
|
+
sequel.run "CREATE TABLE t3(id INT)"
|
338
|
+
|
339
|
+
m2m = Mysql2Mysql.new :from => $from_dsn, :to => $to_dsn, :tables => {/^(db_m2m)/ => /^t[0-9]$/}
|
340
|
+
m2m.dump
|
341
|
+
from_db = m2m.instance_variable_get('@from_db')
|
342
|
+
to_db = m2m.instance_variable_get('@to_db')
|
343
|
+
|
344
|
+
to_db.fetch("SHOW DATABASES").all.collect{|row|row.values.first}.should include(db1_name)
|
345
|
+
to_db.fetch("SHOW DATABASES").all.collect{|row|row.values.first}.should include(db2_name)
|
346
|
+
to_db.fetch("SHOW DATABASES").all.collect{|row|row.values.first}.should_not include(db3_name)
|
347
|
+
|
348
|
+
to_db.run "use #{db1_name}"
|
349
|
+
to_db.tables.should include(:t1)
|
350
|
+
to_db.tables.should include(:t2)
|
351
|
+
to_db.tables.should_not include(:t10)
|
352
|
+
to_db.run "use #{db2_name}"
|
353
|
+
to_db.tables.should include(:t3)
|
354
|
+
|
355
|
+
from_db.run "DROP DATABASE #{db1_name}"
|
356
|
+
from_db.run "DROP DATABASE #{db2_name}"
|
357
|
+
from_db.run "DROP DATABASE #{db3_name}"
|
358
|
+
to_db.run "DROP DATABASE #{db1_name}"
|
359
|
+
to_db.run "DROP DATABASE #{db2_name}"
|
360
|
+
sequel.disconnect
|
361
|
+
disconnect(m2m)
|
362
|
+
end
|
363
|
+
|
364
|
+
it "can clone one database to another database in the same server" do
|
365
|
+
dbname = 'db_m2m'
|
366
|
+
new_dbname = 'db_2m2'
|
367
|
+
|
368
|
+
sequel = Sequel.connect $from_dsn
|
369
|
+
sequel.run "DROP DATABASE IF EXISTS #{dbname}"
|
370
|
+
sequel.run "DROP DATABASE IF EXISTS #{new_dbname}"
|
371
|
+
sequel.run "CREATE DATABASE #{dbname}"
|
372
|
+
sequel.run "USE #{dbname}"
|
373
|
+
sequel.run "CREATE TABLE t1(id INT)"
|
374
|
+
sequel.run "CREATE TABLE t2(id INT)"
|
375
|
+
|
376
|
+
m2m = Mysql2Mysql.new :from => $from_dsn, :to => $from_dsn, :tables => dbname
|
377
|
+
m2m.dump({
|
378
|
+
:before_each => lambda do |db, tb|
|
379
|
+
return new_dbname, "new_#{tb}"
|
380
|
+
end
|
381
|
+
})
|
382
|
+
from_db = m2m.instance_variable_get('@from_db')
|
383
|
+
to_db = m2m.instance_variable_get('@to_db')
|
384
|
+
|
385
|
+
to_db.fetch("SHOW DATABASES").all.collect{|row|row.values.first}.should include(dbname)
|
386
|
+
to_db.fetch("SHOW DATABASES").all.collect{|row|row.values.first}.should include(new_dbname)
|
387
|
+
to_db.run "use #{dbname}"
|
388
|
+
to_db.tables.should include(:t1)
|
389
|
+
to_db.tables.should include(:t2)
|
390
|
+
to_db.run "use #{new_dbname}"
|
391
|
+
to_db.tables.should include(:new_t1)
|
392
|
+
to_db.tables.should include(:new_t2)
|
393
|
+
|
394
|
+
from_db.run "DROP DATABASE #{dbname}"
|
395
|
+
from_db.run "DROP DATABASE #{new_dbname}"
|
396
|
+
to_db.fetch("SHOW DATABASES").all.collect{|row|row.values.first}.should_not include(dbname)
|
397
|
+
to_db.fetch("SHOW DATABASES").all.collect{|row|row.values.first}.should_not include(new_dbname)
|
398
|
+
|
399
|
+
sequel.disconnect
|
400
|
+
from_db.disconnect
|
401
|
+
end
|
402
|
+
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 0.0.
|
8
|
+
- 2
|
9
|
+
version: 0.0.2
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- xianhua.zhou
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-11-
|
17
|
+
date: 2010-11-28 00:00:00 +08:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -61,6 +61,7 @@ files:
|
|
61
61
|
- INSTALL
|
62
62
|
- lib/mysql2mysql.rb
|
63
63
|
- lib/mysql_2_mysql.rb
|
64
|
+
- spec/mysql_2_mysql_spec.rb
|
64
65
|
has_rdoc: true
|
65
66
|
homepage: http://github.com/xianhuazhou
|
66
67
|
licenses: []
|