mysql2mysql 0.0.1 → 0.0.2
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 +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: []
|