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 CHANGED
@@ -1,2 +1,5 @@
1
+ v0.0.2
2
+ lots of small fixes and improvements
3
+
1
4
  v0.0.1
2
- first release
5
+ first release
data/INSTALL CHANGED
@@ -6,4 +6,4 @@ true
6
6
  > require 'mysql2mysql'
7
7
  true
8
8
  > Mysql2Mysql.version
9
- 0.0.1
9
+ 0.0.2
@@ -1,19 +1,141 @@
1
1
  == Description
2
2
 
3
- dump table's structure and data between mysql servers and databases.
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('test' => :all, 'mysql' => /^time_zone/).
12
- dump(:charset => 'utf8', 'with_data' => true)
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.1
134
+ v0.0.2
135
+
136
+ == TODO
137
+
138
+ more docs and specs
17
139
 
18
140
  == Author
19
141
 
@@ -9,7 +9,7 @@
9
9
  #
10
10
  # == Version
11
11
  #
12
- # v0.0.1
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.1'
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 => 'utf8',
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 => false,
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
- ].each do |sql|
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[:after_all].respond_to? :call
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
- # create database
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
- # create table
140
- unless @to_db.table_exists? to_table.to_sym
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.gsub("`#{from_table}`", "`#{to_table}`"), :on_connection => @to_db
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
- # dump data
151
- @from_db.fetch("SELECT * FROM #{from_table}").each do |row|
152
- @to_db[to_table.to_sym].insert row
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 @from
166
- @to_db = db_connection @to
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
- filter_tables
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 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)
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
- 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
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
- filter all_valid_tables
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 = 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
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.reject do |dbname, tbname|
271
- reject_action.call(dbname, tbname)
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
- - 1
9
- version: 0.0.1
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-18 00:00:00 +08:00
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: []