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 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: []