db-copier 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.textile ADDED
@@ -0,0 +1,75 @@
1
+ h2. DbCopier
2
+
3
+ DbCopier is a DSL around Sequel for quickly making back-ups/copies across various database systems with minimal fuss:
4
+
5
+ #my_copy_script.rb
6
+ require 'rubygems'
7
+ require 'db-copier'
8
+ source_db => {:adapter => 'mysql', :host => 'localhost', :user => 'root', :password => '', :database => 'db_copier_test_src'}
9
+ target_db => {:adapter => 'mysql', :host => 'localhost', :user => 'root', :password => '', :database => 'db_copier_test_target'}
10
+ DbCopier.app do
11
+   copy :from => source_db, :to => target_db
12
+ end
13
+
14
+ This would create tables and indexes in the target_db for you (if they do not exist) and copy over contents from the source and best of all it <notextile>*works with any database that works with Sequel*</notextile> (which is a pretty damn big set) -- so you can copy your stuff over from mysql over to postgres, no worries.
15
+
16
+ h3. Installation
17
+
18
+ sudo gem install db-copier
19
+
20
+ h3. Options
21
+
22
+ You can specify how many rows you would like to copy at a time via the <notextile>*rows_per_copy*</notextile> (default is 50) attribute.
23
+ copy :from => source_db, :to => target_db, :rows_per_copy => 100 #Copies 100 rows at a time
24
+ You can also specify the maximum number of open db connections you would like via the <notextile>*max_connections*</notextile> attribute (default is 5).
25
+
26
+ copy :from => source_db, :to => target_db, :max_connections => 5 #Maintains a maximum of 5 connections
27
+
28
+ h3. More Granular Control
29
+
30
+ If you would like to copy only certain tables, you can do that as well using the 'only' and 'except' methods.
31
+ Example: Copy only the users table
32
+
33
+   copy :from => source_db, :to => target_db do
34
+     only => 'users'
35
+   end
36
+
37
+ Example: Copy only the users and projects tables
38
+
39
+   copy :from => source_db, :to => target_db do
40
+     only => 'users', 'projects'
41
+   end
42
+
43
+ Example: Copy everything but the users and projects tables
44
+
45
+   copy :from => source_db, :to => target_db do
46
+     except => 'users', 'projects'
47
+   end
48
+
49
+ You can also choose to copy only over certain columns of a table using the for_table method.
50
+
51
+ Example: Copy only the name and id fields of the users table
52
+
53
+   copy :from => source_db, :to => target_db do
54
+     for_table 'users', :copy_columns => ['name','id']
55
+   end
56
+
57
+ You can mix-and-match to your heart's content.
58
+
59
+   copy :from => source_db, :to => target_db, :rows_per_copy => 1000, :max_connections => 10 do
60
+     except => 'departments', 'projects'
61
+     for_table 'users', :copy_columns => ['name','id']
62
+     for_table 'employees', :copy_columns => ['id','age','name']
63
+   end
64
+
65
+ Finally, if you have already created the schema of the target_db db-copier only tries to copy columns that are present in the target_db's schema.
66
+
67
+ h4. LICENSE
68
+
69
+ (The MIT License)
70
+ Copyright (c) 2009:
71
+ "Santosh Kumar"
72
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
73
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
74
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
75
+ 0:0
data/lib/db-copier.rb ADDED
@@ -0,0 +1,9 @@
1
+ libdir = File.dirname(__FILE__)
2
+ $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
3
+
4
+ require 'rubygems'
5
+ require 'sequel'
6
+ require 'term/ansicolor'
7
+ include Term::ANSIColor
8
+ require 'db-copier/db-copier'
9
+ require 'db-copier/worker'
@@ -0,0 +1,113 @@
1
+
2
+
3
+ module DbCopier
4
+ class Application
5
+ attr_reader :tables_to_copy
6
+
7
+ def initialize &block
8
+ @tables_to_copy = []
9
+ @index_to_copy = []
10
+ @only_tables_to_copy = []
11
+ @except_tables_to_copy = []
12
+ instance_eval &block
13
+ end
14
+
15
+ def copy options = {}
16
+ begin
17
+ from, to, @rows_per_copy, max_connections = options[:from], options[:to], (options[:rows_per_copy] || 50), (options[:max_connections] || 5)
18
+ raise ArgumentError unless from && to && from.is_a?(Hash) && to.is_a?(Hash) && from.size > 0 && to.size > 0
19
+ @source_db, @target_db = Sequel.connect(from.merge(:max_connections => max_connections, :single_threaded => false)),
20
+ Sequel.connect(to.merge(:max_connections => max_connections, :single_threaded => false))
21
+ @source_db.test_connection && @target_db.test_connection #test connections
22
+ @tables_to_copy = @source_db.tables
23
+ @target_db.tables
24
+ instance_eval { yield } if block_given?
25
+ copy_tables
26
+ ensure
27
+ self.close_connections
28
+ end
29
+ end
30
+
31
+ def copy_columns
32
+ #Hash of the form {:table => [:col1, :col2]} -- maintaing record of columns to keep for a table
33
+ @copy_columns ||= {}
34
+ end
35
+
36
+ protected :copy_columns
37
+
38
+ def for_table(table, options = {})
39
+ raise ArgumentError, "missing required copy_cols attribute" unless (copy_columns = options[:copy_columns])
40
+ table, copy_columns = table.to_sym, copy_columns.map {|col| col.to_sym}
41
+ raise ArgumentError, "columns do not exist" unless (@source_db.schema(table).map {|cols| cols.first} & copy_columns) == copy_columns
42
+ self.copy_columns[table] = copy_columns
43
+ end
44
+
45
+ def index(ind)
46
+ @index_to_copy << ind
47
+ end
48
+
49
+ def only(*tabs)
50
+ @tables_to_copy = Array(tabs)
51
+ end
52
+
53
+ def except(*tabs)
54
+ @tables_to_copy -= Array(tabs).map { |tb| tb.to_sym }
55
+ end
56
+
57
+ def copy_tables
58
+ threads = []
59
+ multi_threaded = !(@source_db.single_threaded? || @target_db.single_threaded?)
60
+ @tables_to_copy.each do |tab|
61
+ db_src_conn, db_target_con, table_to_copy, copy_columns_for_table =
62
+ @source_db, @target_db, tab.to_sym, self.copy_columns[tab.to_sym]
63
+ if multi_threaded
64
+ t = Thread.new(table_to_copy, db_src_conn, db_target_con, copy_columns_for_table) do
65
+ $stdout.print green, bold, "starting Thread: #{Thread.current.object_id}", reset, "\n"
66
+ w = Worker.new :src_db_conn => db_src_conn, :target_db_conn => db_target_con,
67
+ :table_name => table_to_copy, :copy_columns => copy_columns_for_table
68
+ w.copy_table
69
+ end
70
+ threads << t
71
+ else
72
+ $stdout.print green, bold, "running in single threaded mode", reset, "\n"
73
+ w = Worker.new :src_db_conn => db_src_conn, :target_db_conn => db_target_con,
74
+ :table_name => table_to_copy, :copy_columns => copy_columns_for_table
75
+ w.copy_table
76
+ end
77
+ end
78
+ threads.each {|t| t.join} if multi_threaded
79
+ end
80
+
81
+ protected :copy_tables
82
+
83
+ def close_connections
84
+ @source_db.disconnect if defined?(@source_db) && @source_db
85
+ @target_db.disconnect if defined?(@target_db) && @target_db
86
+ end
87
+
88
+ protected :close_connections
89
+
90
+ end
91
+
92
+ def self.app &block
93
+ Application.new &block
94
+ end
95
+
96
+ def self.generate_create_table_ddl(db_conn, table_to_create_schema_from, new_table_name, only_copy_columns = [])
97
+ ret = String.new
98
+ db_conn.schema(table_to_create_schema_from.to_sym).each do |col|
99
+ col_name, col_type = col[0].to_sym, col[1][:type]
100
+ #Skip creating this column if it was not specified in the columns to copy for this table
101
+ next if only_copy_columns.count > 0 && !only_copy_columns.include?(col_name)
102
+ if col[1][:primary_key]
103
+ ret << "primary_key #{col_name.inspect}, #{col_type.to_sym.inspect}, :default => #{col[1][:default].inspect}, :null => #{col[1][:allow_null].inspect};\n"
104
+ else
105
+ ret << "#{col_type.to_s.capitalize} #{col_name.inspect}, :default => #{col[1][:default].inspect}, :null => #{col[1][:allow_null].inspect};\n"
106
+ end
107
+ end
108
+ ret = (".create_table #{new_table_name.to_sym.inspect} do \n" + ret)
109
+ ret << "end\n"
110
+ end
111
+
112
+ end
113
+
@@ -0,0 +1,51 @@
1
+ module DbCopier
2
+ class Worker
3
+ def initialize(options = {})
4
+ @src_db_conn, @target_db_conn, @table_name, @rows_per_copy, @copy_columns = options[:src_db_conn], options[:target_db_conn],
5
+ options[:table_name], (options[:rows_per_copy] || 1000), (options[:copy_columns] || [])
6
+ raise ArgumentError unless @src_db_conn && @target_db_conn && @table_name
7
+ @copy_columns ||= []
8
+ end
9
+
10
+ def copy_table
11
+ start = Time.now
12
+ $stdout.print green, bold, "Thread: #{Thread.current.object_id} is copying table: #{@table_name}", reset, "\n"
13
+ tab_to_copy = @table_name.to_sym
14
+ num_rows = @src_db_conn[tab_to_copy].count
15
+ i = 0
16
+ #Create the table if it does not already exist
17
+ unless @target_db_conn.table_exists?(tab_to_copy)
18
+ table_creation_ddl = DbCopier.generate_create_table_ddl(@src_db_conn, tab_to_copy, tab_to_copy, @copy_columns)
19
+ table_creation_ddl = ("@target_db_conn" + table_creation_ddl)
20
+ eval table_creation_ddl
21
+ end
22
+
23
+ #This is the intersection of columns specified via the +copy_columns+ argumnent in the +for_table+ method
24
+ #and those that actually exist in the target table.
25
+ columns_in_target_db = @target_db_conn.schema(tab_to_copy).map {|cols| cols.first}
26
+ columns_to_copy =
27
+ if @copy_columns.count > 0
28
+ @copy_columns & columns_in_target_db
29
+ else
30
+ columns_in_target_db
31
+ end
32
+
33
+ while i < num_rows
34
+ rows_to_copy = @src_db_conn[tab_to_copy].select(*columns_to_copy).limit(@rows_per_copy, i).all
35
+ #Special handling of datetime columns
36
+ rows_to_copy.each { |col_name, col_val| rows_to_copy[col_name] = DateTime.parse(col_val) if col_val.class == Time }
37
+ i += rows_to_copy.count
38
+ @target_db_conn[tab_to_copy].multi_insert(rows_to_copy)
39
+ end
40
+
41
+ #copy indexes now
42
+ @src_db_conn.indexes(tab_to_copy).each do |index_name, index_info|
43
+ #Make sure we are adding an index to a column that is going to be there
44
+ next unless (columns_to_copy & index_info[:columns] == index_info[:columns])
45
+ @target_db_conn.add_index(tab_to_copy, index_info[:columns])
46
+ end
47
+ $stdout.print green,bold, "Thread: #{Thread.current.object_id} has COMPLETED copying table: #{@table_name} in #{Time.now - start} seconds", reset, "\n"
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,254 @@
1
+ require File.dirname(__FILE__) + "/spec_helper.rb"
2
+
3
+ describe DbCopier do
4
+ source_db = {:adapter => 'mysql', :host => 'localhost', :user => 'root', :password => '', :database => 'db_copier_test_src'}
5
+ target_db = source_db.merge(:database => 'db_copier_test_target')
6
+ fake_cred_db = target_db.merge(:user => 'rootsss')
7
+
8
+ def create_and_populate_table(conn, tbl_name, num_rows = 1000)
9
+ conn.create_table tbl_name do
10
+ primary_key :id, :integer, :null => false
11
+ String :nombre, :null => true
12
+ DateTime :created_at, :null => false, :default => DateTime.now
13
+ end
14
+ rows = []
15
+ num_rows.times {|i| rows << {:id => (i+1), :nombre => Faker::Name.name, :created_at => DateTime.now}}
16
+ conn[tbl_name].multi_insert rows
17
+ end
18
+
19
+ before(:all) do
20
+ #create some connections
21
+ @source_db_conn, @fake_cred_db_conn, @target_db_conn = Sequel.connect(source_db), Sequel.connect(fake_cred_db),
22
+ Sequel.connect(target_db)
23
+
24
+ create_and_populate_table @source_db_conn, :uno
25
+ create_and_populate_table @source_db_conn, :dos
26
+ create_and_populate_table @source_db_conn, :tres
27
+ create_and_populate_table @source_db_conn, :quatro
28
+ create_and_populate_table @source_db_conn, :quinto
29
+ end
30
+
31
+ after(:each) do
32
+ @target_db_conn.tables.each { |tbl| @target_db_conn.drop_table(tbl) } #clear the target tables
33
+ end
34
+
35
+ after(:all) do
36
+ @source_db_conn.tables.each { |tab| @source_db_conn.drop_table tab } #clear the source tables
37
+ end
38
+
39
+ def create_target_tables
40
+ @target_db_conn.create_table :uno do
41
+ primary_key :id, :integer, :null => false
42
+ String :nombre, :null => true
43
+ DateTime :created_at, :null => false
44
+ end
45
+ @target_db_conn.create_table :dos do
46
+ primary_key :id, :integer, :null => false
47
+ String :nombre, :null => true
48
+ DateTime :created_at, :null => false
49
+ end
50
+ @target_db_conn.create_table :tres do
51
+ primary_key :id, :integer, :null => false
52
+ String :nombre, :null => true
53
+ DateTime :created_at, :null => false
54
+ end
55
+ end
56
+
57
+ def create_target_with_one_less_column
58
+ @target_db_conn.create_table :uno do
59
+ primary_key :id, :integer, :null => false
60
+ String :nombre, :null => true
61
+ end
62
+ end
63
+
64
+ it "should throw an argument error if the +:from+ and +:to+ arguments are not provided for the +copy+ method" do
65
+ create_target_tables
66
+ begin
67
+ DbCopier.app do
68
+ copy
69
+ end
70
+ raise RuntimeError, "shouldn't be here"
71
+ rescue ArgumentError
72
+ end
73
+
74
+ begin
75
+ DbCopier.app do
76
+ copy :from => {:adapter => 'mysql'}
77
+ end
78
+ raise RuntimeError, "shouldn't be here"
79
+ rescue ArgumentError
80
+ end
81
+
82
+ begin
83
+ DbCopier.app do
84
+ copy :to => {:adapter => 'mysql'}
85
+ end
86
+ raise RuntimeError, "shouldn't be here"
87
+ rescue ArgumentError
88
+ end
89
+ end
90
+
91
+ it "should throw an argument error if the +:from+ and +:to+ arguments are NOT hashes" do
92
+ create_target_tables
93
+ begin
94
+ DbCopier.app do
95
+ copy :from => 'foo', :to => 'bar'
96
+ end
97
+ raise RuntimeError, "shouldn't be here"
98
+ rescue ArgumentError
99
+ end
100
+ end
101
+
102
+ it "should throw an Sequel::DatabaseConnectionError for invalid credentials" do
103
+ create_target_tables
104
+ begin
105
+ DbCopier.app do
106
+ copy :from => source_db, :to => fake_cred_db
107
+ end
108
+ raise RuntimeError, "shouldn't be here"
109
+ rescue Sequel::DatabaseConnectionError
110
+ end
111
+ end
112
+
113
+ it "should copy all tables if no #table methods are called in the dsl" do
114
+ create_target_tables
115
+ app = DbCopier.app do
116
+ copy :from => source_db, :to => target_db
117
+ end
118
+ @source_db_conn.tables.map{|tbl| tbl.inspect}.sort.should == @target_db_conn.tables.map{|tbl| tbl.inspect}.sort
119
+ end
120
+
121
+ it "should copy only select tables if they are provided as an #only method in the dsl" do
122
+ create_target_tables
123
+ only_tables_to_copy = ['uno', 'dos'].sort
124
+ app = DbCopier.app do
125
+ copy :from => source_db, :to => target_db do
126
+ only *only_tables_to_copy
127
+ end
128
+ end
129
+ only_tables_to_copy.each { |tbl| @source_db_conn[tbl.to_sym].count.should == @target_db_conn[tbl.to_sym].count }
130
+ end
131
+
132
+ it "should not copy tables specified in the #except dsl method" do
133
+ create_target_tables
134
+ original_tres_count = @target_db_conn[:tres].count
135
+ app = DbCopier.app do
136
+ copy :from => source_db, :to => target_db do
137
+ except 'tres'
138
+ end
139
+ end
140
+ @target_db_conn[:tres].count.should == original_tres_count
141
+ end
142
+
143
+ it "should throw an ArgumentError if the for_table method is called without appropriate args" do
144
+ create_target_tables
145
+ begin
146
+ app = DbCopier.app do
147
+ copy :from => source_db, :to => target_db do
148
+ except 'uno', 'dos'
149
+ for_table :custom_src_query_ratings #Missing :copy_columns argument
150
+ end
151
+ end
152
+ raise RuntimeError, "Shouldn't be here"
153
+ rescue ArgumentError
154
+ end
155
+ end
156
+
157
+ it "should only copy the columns specified in the +copy_columns+ argument in the for_table method" do
158
+ create_target_tables
159
+ app = DbCopier.app do
160
+ copy :from => source_db, :to => target_db do
161
+ except 'tres'
162
+ for_table :uno, :copy_columns => ['id', 'created_at']
163
+ end
164
+ end
165
+ @target_db_conn[:uno].all.each do |row|
166
+ row[:nombre].should == nil
167
+ end
168
+ end
169
+
170
+ it "should throw an ArgumentError if the columns specified in the for_table method does not exist do not exist in the source table" do
171
+ create_target_tables
172
+ begin
173
+ app = DbCopier.app do
174
+ copy :from => source_db, :to => target_db do
175
+ except 'tres'
176
+ for_table :uno, :copy_columns => ['id', 'foo']
177
+ end
178
+ end
179
+ raise RuntimeError, "Shouldn't be here"
180
+ rescue ArgumentError
181
+ end
182
+ end
183
+
184
+ it "should copy only columns that are there in the target db" do
185
+ create_target_with_one_less_column
186
+ app = DbCopier.app do
187
+ copy :from => source_db, :to => target_db do
188
+ only 'uno'
189
+ end
190
+ end
191
+ @source_db_conn[:uno].count.should == @target_db_conn[:uno].count
192
+ end
193
+
194
+ it "should create tables in the target db if they do not exist" do
195
+ app = DbCopier.app do
196
+ copy :from => source_db, :to => target_db do
197
+ end
198
+ end
199
+ @target_db_conn.tables.map{|tbl| tbl.to_s}.sort.should == @source_db_conn.tables.map{|tbl| tbl.to_s}.sort
200
+ @source_db_conn.tables.each do |tbl|
201
+ @source_db_conn[tbl].count.should == @target_db_conn[tbl].count
202
+ end
203
+ end
204
+
205
+ it "should create tables in the target db if they do not exist and it should support a +for_table+ argument" do
206
+ app = DbCopier.app do
207
+ copy :from => source_db, :to => target_db do
208
+ for_table :uno, :copy_columns => ['id', 'created_at']
209
+ end
210
+ end
211
+ @target_db_conn.tables.map{|tbl| tbl.to_s}.sort.should == @source_db_conn.tables.map{|tbl| tbl.to_s}.sort
212
+ @source_db_conn.tables.each do |tbl|
213
+ @source_db_conn[tbl].count.should == @target_db_conn[tbl].count
214
+ end
215
+ end
216
+
217
+ it "should copy indexes" do
218
+ #Create an index
219
+ @source_db_conn.add_index :uno, :nombre
220
+ app = DbCopier.app do
221
+ copy :from => source_db, :to => target_db do
222
+ only 'uno'
223
+ end
224
+ end
225
+
226
+ @target_db_conn.indexes(:uno).count.should > 0
227
+ @target_db_conn.indexes(:uno).each { |key, val| val[:columns].should include(:nombre) }
228
+ @source_db_conn.drop_index :uno, :nombre #clean-up
229
+
230
+ @source_db_conn.add_index :dos, [:nombre, :created_at]
231
+ app = DbCopier.app do
232
+ copy :from => source_db, :to => target_db do
233
+ only 'dos'
234
+ end
235
+ end
236
+ @target_db_conn.indexes(:dos).count.should > 0
237
+ @target_db_conn.indexes(:dos).each { |key, val| val[:columns].should == [:nombre,:created_at] }
238
+ end
239
+
240
+ it "should not copy indexes of columns that are not to be copied" do
241
+ #Create an index
242
+ @source_db_conn.add_index :uno, :nombre
243
+ app = DbCopier.app do
244
+ copy :from => source_db, :to => target_db do
245
+ only 'uno'
246
+ for_table :uno, :copy_columns => [:id,:created_at]
247
+ end
248
+ end
249
+ @target_db_conn.indexes(:uno).count.should == 0
250
+ end
251
+
252
+ it "should contain useful log msgs"
253
+
254
+ end
@@ -0,0 +1,4 @@
1
+ require 'rubygems'
2
+ require "faker"
3
+ require File.dirname(__FILE__) + "/../lib/db-copier"
4
+
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: db-copier
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Santosh Kumar
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-01-18 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: sequel
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 3.8.0
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: term-ansicolor
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.0.4
34
+ version:
35
+ description:
36
+ email: santosh79@gmail.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ files:
44
+ - lib/db-copier.rb
45
+ - lib/db-copier/db-copier.rb
46
+ - lib/db-copier/worker.rb
47
+ - README.textile
48
+ - spec/spec_helper.rb
49
+ - spec/basic_spec.rb
50
+ has_rdoc: true
51
+ homepage: http://github.com/santosh79/db-copier
52
+ licenses: []
53
+
54
+ post_install_message:
55
+ rdoc_options: []
56
+
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ version:
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: "0"
70
+ version:
71
+ requirements: []
72
+
73
+ rubyforge_project:
74
+ rubygems_version: 1.3.5
75
+ signing_key:
76
+ specification_version: 3
77
+ summary: A DSL around Sequel to aid in copying or replicating databases.
78
+ test_files: []
79
+