wyrm 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +22 -3
- data/README.md +12 -5
- data/lib/wyrm/db_pump.rb +102 -62
- data/lib/wyrm/dump_schema.rb +11 -32
- data/lib/wyrm/pump_maker.rb +2 -4
- data/lib/wyrm/restore_schema.rb +45 -15
- data/lib/wyrm/version.rb +1 -1
- data/snippets/console.rb +0 -2
- data/wyrm.gemspec +1 -1
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6c0bb0fe99a301ead2da2ce8a64dc1eb20c925b0
|
4
|
+
data.tar.gz: 031b66ab01f20c5ebad94dbfa3c50338dbd15cba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5feadc5c19a9df8417414cb91270ae55b8d114cf94fc9daa33b01b2f23292e858a109b0d703cfc7474952b963c26876d7e732f12c0802146cd5ef7838e803629
|
7
|
+
data.tar.gz: a73a7c30e43a430fb05d22552f5b0f10c81cdcd99ec818ff8f838f6ff1a6f59f3c9d71a9d8eacdd26ff4066f1140e82e0236db0e13c2421fba8b1800b7d35710
|
data/Gemfile
CHANGED
@@ -1,7 +1,26 @@
|
|
1
|
-
|
2
|
-
#
|
1
|
+
def from_gemrc
|
2
|
+
# auto-load from ~/.gemrc
|
3
|
+
home_gemrc = Pathname('~/.gemrc').expand_path
|
3
4
|
|
4
|
-
|
5
|
+
if home_gemrc.exist?
|
6
|
+
require 'yaml'
|
7
|
+
# use all the sources specified in .gemrc
|
8
|
+
YAML.load_file(home_gemrc)[:sources]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# Use the gemrc source if defined, unless CANON is set,
|
13
|
+
# otherwise just use the default.
|
14
|
+
def preferred_sources
|
15
|
+
rv = from_gemrc unless eval(ENV['CANON']||'')
|
16
|
+
rv ||= []
|
17
|
+
rv << 'http://rubygems.org' if rv.empty?
|
18
|
+
rv
|
19
|
+
end
|
20
|
+
|
21
|
+
preferred_sources.each{|src| source src}
|
22
|
+
|
23
|
+
gem 'sequel'
|
5
24
|
gem 'fastandand'
|
6
25
|
|
7
26
|
# Specify your gem's dependencies in wyrm.gemspec
|
data/README.md
CHANGED
@@ -3,16 +3,23 @@
|
|
3
3
|
Transfer data from one database to another. Has been used to dump > 100M dbs,
|
4
4
|
and one 850G db. Should theoretically work for any dbs supported by Sequel.
|
5
5
|
|
6
|
+
Dumps are compressed with bz2, using pbzip2. Fast *and* small :-D For example:
|
7
|
+
mysqldump | bzip2 for a certain 850G db comes to 127G. With wyrm it
|
8
|
+
comes to 134G.
|
9
|
+
|
6
10
|
Currently transfers tables and views only. Does not attempt to transfer
|
7
11
|
stored procs, permissions, triggers etc.
|
8
12
|
|
9
|
-
|
10
|
-
|
13
|
+
Handles tables with a single numeric key, single non-numeric key, and no
|
14
|
+
primary key. Haven't tried with compound primary key.
|
15
|
+
|
16
|
+
Depending on table keys will use different strategies to keep memory usage small.
|
17
|
+
Will use result set streaming if available.
|
11
18
|
|
12
19
|
Wyrm because:
|
13
20
|
|
14
21
|
- I like dragons
|
15
|
-
- I can (eventually) have a Wyrm::Hole to transfer data through
|
22
|
+
- I can (eventually) have a Wyrm::Hole to transfer data through ;-)
|
16
23
|
|
17
24
|
## Dependencies
|
18
25
|
|
@@ -37,7 +44,7 @@ Or install it yourself as:
|
|
37
44
|
|
38
45
|
Make sure you install the db gems, typically
|
39
46
|
|
40
|
-
$ gem install pg sequel_pg mysql2
|
47
|
+
$ gem install pg sequel_pg mysql2 sqlite3
|
41
48
|
|
42
49
|
## Usage
|
43
50
|
|
@@ -77,7 +84,7 @@ require 'wyrm/db_pump'
|
|
77
84
|
|
78
85
|
db = Sequel.connect 'postgres://postgres@localhost/other_db'
|
79
86
|
dbp = DbPump.new db, :things
|
80
|
-
dbp.
|
87
|
+
dbp.io = IO.popen 'pbzip2 -d -c /mnt/disk/wyrm/things.dbp.bz2'
|
81
88
|
dbp.each_row do |row|
|
82
89
|
puts row.inspect
|
83
90
|
end
|
data/lib/wyrm/db_pump.rb
CHANGED
@@ -1,20 +1,16 @@
|
|
1
1
|
require 'sequel'
|
2
2
|
require 'yaml'
|
3
|
-
require 'ostruct'
|
4
3
|
require 'logger'
|
5
|
-
require 'fastandand'
|
6
4
|
|
7
5
|
Sequel.extension :migration
|
8
6
|
|
9
|
-
# TODO possibly use Gem::Package::TarWriter to write tar files
|
10
7
|
# TODO when restoring, could use a SizeQueue to make sure the db is kept busy
|
11
|
-
|
12
8
|
# TODO need to version the dumps, or something like that.
|
13
|
-
# TODO
|
14
|
-
#
|
9
|
+
# TODO looks like io should belong to codec. Hmm. Not sure.
|
10
|
+
# TODO table_name table_dataset need some thinking about. Dataset would encapsulate both. But couldn't change db then, and primary_keys would be hard.
|
15
11
|
class DbPump
|
16
12
|
# some codecs might ignore io, eg if a dbpump is talking to another dbpump
|
17
|
-
def initialize( db, table_name, io: STDOUT, codec: :marshal, page_size: 10000, dry_run: false )
|
13
|
+
def initialize( db: nil, table_name: nil, io: STDOUT, codec: :marshal, page_size: 10000, dry_run: false )
|
18
14
|
self.codec = codec
|
19
15
|
self.db = db
|
20
16
|
self.table_name = table_name
|
@@ -42,14 +38,24 @@ class DbPump
|
|
42
38
|
|
43
39
|
def db=( other_db )
|
44
40
|
invalidate_cached_members
|
41
|
+
|
45
42
|
@db = other_db
|
43
|
+
return unless other_db
|
44
|
+
|
45
|
+
# add extensions
|
46
46
|
@db.extension :pagination
|
47
|
+
|
48
|
+
# turn on postgres streaming if available
|
49
|
+
if defined?( Sequel::Postgres ) && Sequel::Postgres.supports_streaming?
|
50
|
+
logger.info "Turn streaming on for postgres"
|
51
|
+
@db.extension :pg_streaming
|
52
|
+
end
|
47
53
|
end
|
48
54
|
|
49
55
|
# return an object that responds to ===
|
50
56
|
# which returns true if ==='s parameter
|
51
57
|
# responds to all the methods
|
52
|
-
def quacks_like( *methods )
|
58
|
+
def self.quacks_like( *methods )
|
53
59
|
@quacks_like ||= {}
|
54
60
|
@quacks_like[methods] ||= Object.new.tap do |obj|
|
55
61
|
obj.define_singleton_method(:===) do |instance|
|
@@ -58,6 +64,10 @@ class DbPump
|
|
58
64
|
end
|
59
65
|
end
|
60
66
|
|
67
|
+
def quacks_like( *methods )
|
68
|
+
self.class.quacks_like( *methods )
|
69
|
+
end
|
70
|
+
|
61
71
|
def codec=( codec_thing )
|
62
72
|
@codec =
|
63
73
|
case codec_thing
|
@@ -68,7 +78,7 @@ class DbPump
|
|
68
78
|
when quacks_like( :encode, :decode )
|
69
79
|
codec_thing
|
70
80
|
else
|
71
|
-
raise "unknown codec #{codec_thing}"
|
81
|
+
raise "unknown codec #{codec_thing.inspect}"
|
72
82
|
end
|
73
83
|
end
|
74
84
|
|
@@ -110,44 +120,75 @@ class DbPump
|
|
110
120
|
@table_dataset ||= db[table_name.to_sym]
|
111
121
|
end
|
112
122
|
|
113
|
-
#
|
114
|
-
|
115
|
-
# because mysql is useless
|
116
|
-
def paginated_dump
|
123
|
+
# Use limit / offset. Last fallback if there are no keys (or a compound primary key?).
|
124
|
+
def paginated_dump( &encode_block )
|
117
125
|
table_dataset.order(*primary_keys).each_page(page_size) do |page|
|
118
126
|
logger.info page.sql
|
119
|
-
page.each
|
120
|
-
unless dry_run?
|
121
|
-
codec.encode row.values, io
|
122
|
-
end
|
123
|
-
end
|
127
|
+
page.each &encode_block
|
124
128
|
end
|
125
129
|
end
|
126
130
|
|
127
|
-
#
|
131
|
+
# Use limit / offset, but not for all fields.
|
128
132
|
# The idea is that large offsets are expensive in the db because the db server has to read
|
129
|
-
# through the data set to reach the required offset. So make that only ids
|
130
|
-
# do the main select from the limited id list.
|
133
|
+
# through the data set to reach the required offset. So make that only ids need to be read,
|
134
|
+
# and then do the main select from the limited id list.
|
131
135
|
# TODO could speed this up by have a query thread which runs the next page-query while
|
132
136
|
# the current one is being written/compressed.
|
133
137
|
# select * from massive as full
|
134
138
|
# inner join (select id from massive order by whatever limit m, n) limit
|
135
139
|
# on full.id = limit.id
|
136
140
|
# order by full.whatever
|
137
|
-
|
141
|
+
# http://www.numerati.com/2012/06/26/reading-large-result-sets-with-hibernate-and-mysql/
|
142
|
+
def inner_dump( &encode_block )
|
138
143
|
# could possibly overrride Dataset#paginate(page_no, page_size, record_count=nil)
|
139
144
|
0.step(table_dataset.count, page_size).each do |offset|
|
140
145
|
limit_dataset = table_dataset.select( *primary_keys ).limit( page_size, offset ).order( *primary_keys )
|
141
146
|
page = table_dataset.join( limit_dataset, Hash[ primary_keys.map{|f| [f,f]} ] ).order( *primary_keys ).qualify(table_name)
|
142
147
|
logger.info page.sql
|
143
|
-
page.each
|
144
|
-
|
145
|
-
|
148
|
+
page.each &encode_block
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Selects pages by a range of ids, using >= and <.
|
153
|
+
# Use this for integer pks
|
154
|
+
def min_max_dump( &encode_block )
|
155
|
+
# select max(id), min(id) from table
|
156
|
+
# and then split that up into 10000 size chunks.
|
157
|
+
# Not really important if there aren't exactly 10000
|
158
|
+
min, max = table_dataset.select{[min(id), max(id)]}.first.values
|
159
|
+
return unless min && max
|
160
|
+
|
161
|
+
# will always include the last item because page_size will be
|
162
|
+
# bigger than max for the last page
|
163
|
+
(min..max).step(page_size).each do |offset|
|
164
|
+
page = table_dataset.where( id: offset...(offset + page_size) )
|
165
|
+
logger.info page.sql
|
166
|
+
page.each &encode_block
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def stream_dump( &encode_block )
|
171
|
+
logger.info "using result set streaming"
|
172
|
+
|
173
|
+
# I want to output progress every page_size records,
|
174
|
+
# without doing a records_count % page_size every iteration.
|
175
|
+
# So define an external enumerator
|
176
|
+
# TODO should really performance test the options here.
|
177
|
+
records_count = 0
|
178
|
+
enum = table_dataset.stream.enum_for
|
179
|
+
loop do
|
180
|
+
begin
|
181
|
+
page_size.times do
|
182
|
+
encode_block.call enum.next
|
183
|
+
records_count += 1
|
146
184
|
end
|
185
|
+
ensure
|
186
|
+
logger.info "#{records_count} from #{table_dataset.sql}"
|
147
187
|
end
|
148
188
|
end
|
149
189
|
end
|
150
190
|
|
191
|
+
# Dump the serialization of the table to the specified io.
|
151
192
|
# TODO need to also dump a first row containing useful stuff:
|
152
193
|
# - source table name
|
153
194
|
# - number of rows
|
@@ -155,50 +196,50 @@ class DbPump
|
|
155
196
|
# - permissions?
|
156
197
|
# These should all be in one object that can be Marshall.load-ed easily.
|
157
198
|
def dump
|
199
|
+
_dump do |row|
|
200
|
+
codec.encode( row.values, io ) unless dry_run?
|
201
|
+
end
|
202
|
+
ensure
|
203
|
+
io.flush
|
204
|
+
end
|
205
|
+
|
206
|
+
# decide which kind of paged iteration will be best for this table.
|
207
|
+
# Return an iterator, or yield row hashes to the block
|
208
|
+
def _dump( &encode_block )
|
209
|
+
return enum_for(__method__) unless block_given?
|
158
210
|
case
|
211
|
+
when table_dataset.respond_to?( :stream )
|
212
|
+
stream_dump &encode_block
|
159
213
|
when primary_keys.empty?
|
160
|
-
paginated_dump
|
214
|
+
paginated_dump &encode_block
|
161
215
|
when primary_keys.all?{|i| i == :id }
|
162
|
-
min_max_dump
|
216
|
+
min_max_dump &encode_block
|
163
217
|
else
|
164
|
-
inner_dump
|
218
|
+
inner_dump &encode_block
|
165
219
|
end
|
166
|
-
io.flush
|
167
220
|
end
|
168
221
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
# could possibly overrride Dataset#paginate(page_no, page_size, record_count=nil)
|
176
|
-
# TODO definitely need to refactor this
|
177
|
-
|
178
|
-
# will always include the last item because
|
179
|
-
(min..max).step(page_size).each do |offset|
|
180
|
-
page = table_dataset.where( id: offset...(offset + page_size) )
|
181
|
-
logger.info page.sql
|
182
|
-
page.each do |row|
|
183
|
-
unless dry_run?
|
184
|
-
codec.encode row.values, io
|
185
|
-
end
|
186
|
-
end
|
187
|
-
end
|
222
|
+
def dump_matches_columns?( row_enum, columns )
|
223
|
+
raise "schema mismatch" unless row_enum.peek.size == columns.size
|
224
|
+
true
|
225
|
+
rescue StopIteration
|
226
|
+
# peek threw a StopIteration, so there's no data
|
227
|
+
false
|
188
228
|
end
|
189
229
|
|
190
|
-
# TODO
|
230
|
+
# TODO don't generate the full insert, ie leave out the fields
|
231
|
+
# because we've already checked that the columns and the table
|
232
|
+
# match.
|
233
|
+
# TODO generate column names in insert, they might still work
|
234
|
+
# if columns have been added to the db, but not the dump.
|
191
235
|
# start_row is zero-based
|
192
236
|
def restore( start_row: 0, filename: 'io' )
|
193
237
|
columns = table_dataset.columns
|
194
|
-
logger.info{ "inserting to #{table_name} #{columns.inspect}" }
|
195
|
-
|
196
|
-
# get the Enumerator
|
197
238
|
row_enum = each_row
|
198
239
|
|
199
|
-
|
200
|
-
raise "schema mismatch" if row_enum.peek.size != columns.size
|
240
|
+
return unless dump_matches_columns?( row_enum, columns )
|
201
241
|
|
242
|
+
logger.info{ "inserting to #{table_name} #{columns.inspect}" }
|
202
243
|
rows_restored = 0
|
203
244
|
|
204
245
|
if start_row != 0
|
@@ -217,7 +258,10 @@ class DbPump
|
|
217
258
|
db.transaction do
|
218
259
|
begin
|
219
260
|
page_size.times do
|
220
|
-
# This skips all the checks in the Sequel code
|
261
|
+
# This skips all the checks in the Sequel code. Basically we want
|
262
|
+
# to generate the
|
263
|
+
# insert into (field1,field2) values (value1,value2)
|
264
|
+
# statement as quickly as possible.
|
221
265
|
sql = table_dataset.clone( columns: columns, values: row_enum.next ).send( :clause_sql, :insert )
|
222
266
|
db.execute sql unless dry_run?
|
223
267
|
rows_restored += 1
|
@@ -235,18 +279,14 @@ class DbPump
|
|
235
279
|
rows_restored
|
236
280
|
end
|
237
281
|
|
238
|
-
#
|
239
|
-
|
240
|
-
io.andand.close if io != STDOUT && !io.andand.closed?
|
241
|
-
self.io = IO.popen( "pbzip2 -d -c #{filename}" )
|
242
|
-
end
|
243
|
-
|
244
|
-
# enumerate through the given io at its current position
|
282
|
+
# Enumerate through the given io at its current position
|
283
|
+
# TODO don't check for io.eof here, leave that to the codec
|
245
284
|
def each_row
|
246
285
|
return enum_for(__method__) unless block_given?
|
247
286
|
yield codec.decode( io ) until io.eof?
|
248
287
|
end
|
249
288
|
|
289
|
+
# Enumerate sql insert statements from the dump
|
250
290
|
def insert_sql_each
|
251
291
|
return enum_for(__method__) unless block_given?
|
252
292
|
each_row do |row|
|
data/lib/wyrm/dump_schema.rb
CHANGED
@@ -6,6 +6,7 @@ require 'wyrm/pump_maker'
|
|
6
6
|
# ds = DumpSchema.new src_db, Pathname('/var/data/lots')
|
7
7
|
# ds.dump_schema
|
8
8
|
# ds.dump_tables
|
9
|
+
# TODO possibly use Gem::Package::TarWriter to write tar files
|
9
10
|
class DumpSchema
|
10
11
|
include PumpMaker
|
11
12
|
|
@@ -31,27 +32,6 @@ class DumpSchema
|
|
31
32
|
@fk_migration ||= src_db.dump_foreign_key_migration(:same_db => same_db)
|
32
33
|
end
|
33
34
|
|
34
|
-
def restore_migration
|
35
|
-
<<-EOF
|
36
|
-
require 'restore_migration'
|
37
|
-
Sequel.migration do
|
38
|
-
def db_pump
|
39
|
-
end
|
40
|
-
|
41
|
-
up do
|
42
|
-
restore_tables
|
43
|
-
end
|
44
|
-
|
45
|
-
down do
|
46
|
-
# from each table clear table
|
47
|
-
each_table do |table_name|
|
48
|
-
db_pump.restore table_name, io: io, db: db
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
EOF
|
53
|
-
end
|
54
|
-
|
55
35
|
def same_db
|
56
36
|
false
|
57
37
|
end
|
@@ -61,24 +41,22 @@ class DumpSchema
|
|
61
41
|
end
|
62
42
|
|
63
43
|
def dump_schema
|
64
|
-
|
65
|
-
io.write schema_migration
|
66
|
-
end
|
44
|
+
numbering = '000'
|
67
45
|
|
68
|
-
(container +
|
69
|
-
io.write
|
46
|
+
(container + "#{numbering.next!}_schema.rb").open('w') do |io|
|
47
|
+
io.write schema_migration
|
70
48
|
end
|
71
49
|
|
72
|
-
(container +
|
50
|
+
(container + "#{numbering.next!}_indexes.rb").open('w') do |io|
|
73
51
|
io.write index_migration
|
74
52
|
end
|
75
53
|
|
76
|
-
(container +
|
54
|
+
(container + "#{numbering.next!}_foreign_keys.rb").open('w') do |io|
|
77
55
|
io.write fk_migration
|
78
56
|
end
|
79
57
|
end
|
80
58
|
|
81
|
-
def
|
59
|
+
def write_through_bz2( pathname )
|
82
60
|
fio = pathname.open('w')
|
83
61
|
# open subprocess in read-write mode
|
84
62
|
zio = IO.popen( "pbzip2 -z", 'r+' )
|
@@ -96,7 +74,8 @@ class DumpSchema
|
|
96
74
|
# signal the copier thread to stop
|
97
75
|
zio.close_write
|
98
76
|
logger.debug 'finished dumping'
|
99
|
-
|
77
|
+
|
78
|
+
# wait for copier thread to finish
|
100
79
|
copier.join
|
101
80
|
logger.debug 'stream copy thread finished'
|
102
81
|
ensure
|
@@ -104,7 +83,7 @@ class DumpSchema
|
|
104
83
|
fio.close unless fio.closed?
|
105
84
|
end
|
106
85
|
|
107
|
-
def dump_table( table_name )
|
86
|
+
def dump_table( table_name, &io_block )
|
108
87
|
pump.table_name = table_name
|
109
88
|
if pump.table_dataset.empty?
|
110
89
|
logger.info "No records in #{table_name}"
|
@@ -114,7 +93,7 @@ class DumpSchema
|
|
114
93
|
filename = container + "#{table_name}.dbp.bz2"
|
115
94
|
logger.info "dumping #{table_name} to #{filename}"
|
116
95
|
|
117
|
-
|
96
|
+
write_through_bz2 filename do |zio|
|
118
97
|
# generate the dump
|
119
98
|
pump.io = zio
|
120
99
|
pump.dump
|
data/lib/wyrm/pump_maker.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'wyrm/db_pump'
|
2
2
|
|
3
|
-
|
3
|
+
module PumpMaker
|
4
4
|
def call_or_self( maybe_callable )
|
5
5
|
if maybe_callable.respond_to? :call
|
6
6
|
maybe_callable.call( self )
|
@@ -8,11 +8,9 @@ class Object
|
|
8
8
|
maybe_callable
|
9
9
|
end
|
10
10
|
end
|
11
|
-
end
|
12
11
|
|
13
|
-
module PumpMaker
|
14
12
|
def make_pump( db, pump_thing )
|
15
|
-
call_or_self(pump_thing) || DbPump.new( db
|
13
|
+
call_or_self(pump_thing) || DbPump.new( db: db )
|
16
14
|
end
|
17
15
|
|
18
16
|
def maybe_deebe( db_or_string )
|
data/lib/wyrm/restore_schema.rb
CHANGED
@@ -2,9 +2,9 @@ require 'logger'
|
|
2
2
|
require 'wyrm/pump_maker'
|
3
3
|
|
4
4
|
# Load a schema from a set of dump files (from DumpSchema)
|
5
|
-
# and restore the table data
|
5
|
+
# and restore the table data.
|
6
6
|
# dst_db = Sequel.connect "postgres://localhost:5454/lots"
|
7
|
-
# rs = RestoreSchema.new dst_db,
|
7
|
+
# rs = RestoreSchema.new dst_db, '/var/data/lots'
|
8
8
|
# rs.create
|
9
9
|
# rs.restore_tables
|
10
10
|
class RestoreSchema
|
@@ -14,23 +14,39 @@ class RestoreSchema
|
|
14
14
|
@container = Pathname.new container
|
15
15
|
@dst_db = maybe_deebe dst_db
|
16
16
|
@pump = make_pump( @dst_db, pump )
|
17
|
-
|
18
|
-
load_migrations
|
19
17
|
end
|
20
18
|
|
21
19
|
attr_reader :pump
|
22
20
|
attr_reader :dst_db
|
23
21
|
attr_reader :container
|
24
|
-
attr_reader :schema_migration, :index_migration, :fk_migration
|
25
22
|
|
26
|
-
|
27
|
-
|
23
|
+
# sequel wants migrations numbered, but it's a bit of an annoyance for this.
|
24
|
+
def find_single( glob )
|
25
|
+
candidates =Pathname.glob container + glob
|
26
|
+
raise "too many #{candidates.inspect} for #{glob}" unless candidates.size == 1
|
27
|
+
candidates.first
|
28
|
+
end
|
29
|
+
|
30
|
+
def schema_migration
|
31
|
+
@schema_migration ||= find_single( '*schema.rb' ).read
|
32
|
+
end
|
33
|
+
|
34
|
+
def index_migration
|
35
|
+
@index_migration ||= find_single( '*indexes.rb' ).read
|
36
|
+
end
|
37
|
+
|
38
|
+
def fk_migration
|
39
|
+
@fk_migration ||= find_single( '*foreign_keys.rb' ).read
|
28
40
|
end
|
29
41
|
|
30
|
-
def
|
31
|
-
@
|
32
|
-
@index_migration =
|
33
|
-
@
|
42
|
+
def reload_migrations
|
43
|
+
@fk_migration = nil
|
44
|
+
@index_migration = nil
|
45
|
+
@schema_migration = nil
|
46
|
+
end
|
47
|
+
|
48
|
+
def logger
|
49
|
+
@logger ||= Logger.new STDERR
|
34
50
|
end
|
35
51
|
|
36
52
|
# create indexes and foreign keys, and reset sequences
|
@@ -53,20 +69,34 @@ class RestoreSchema
|
|
53
69
|
eval( schema_migration ).apply dst_db, :up
|
54
70
|
end
|
55
71
|
|
56
|
-
# assume the table name is the base name of table_file
|
72
|
+
# assume the table name is the base name of table_file pathname
|
57
73
|
def restore_table( table_file )
|
58
74
|
logger.info "restoring from #{table_file}"
|
59
75
|
pump.table_name = table_file.basename.sub_ext('').sub_ext('').to_s.to_sym
|
60
76
|
# TODO check if table has been restored already, and has the correct rows,
|
61
|
-
|
62
|
-
IO.popen( "pbzip2 -d -c #{table_file}" ) do |io|
|
77
|
+
open_bz2 table_file do |io|
|
63
78
|
pump.io = io
|
64
79
|
pump.restore
|
65
80
|
end
|
66
81
|
end
|
67
82
|
|
83
|
+
# open a dbp.bz2 file and either yield or return an io of the uncompressed contents
|
84
|
+
def open_bz2( table_name, &block )
|
85
|
+
table_file =
|
86
|
+
case table_name
|
87
|
+
when Symbol
|
88
|
+
container + "#{table_name}.dbp.bz2"
|
89
|
+
when Pathname
|
90
|
+
table_name
|
91
|
+
else
|
92
|
+
raise "Don't know what to do with #{table_name.inspect}"
|
93
|
+
end
|
94
|
+
|
95
|
+
IO.popen "pbzip2 -d -c #{table_file}", &block
|
96
|
+
end
|
97
|
+
|
68
98
|
def restore_tables
|
69
|
-
table_files = Pathname.glob
|
99
|
+
table_files = Pathname.glob container + '*.dbp.bz2'
|
70
100
|
table_files.sort_by{|tf| tf.stat.size}.each{|table_file| restore_table table_file}
|
71
101
|
end
|
72
102
|
end
|
data/lib/wyrm/version.rb
CHANGED
data/snippets/console.rb
CHANGED
@@ -3,8 +3,6 @@ require 'sqlite3'
|
|
3
3
|
require 'pathname'
|
4
4
|
require 'wyrm/dump_schema.rb'
|
5
5
|
|
6
|
-
db = Sequel.connect 'sqlite:/home/panic/.qtstalker/new-trading.sqlite3'
|
7
|
-
|
8
6
|
# pump = DbPump.new db, :positions, codec: :yaml
|
9
7
|
dumper = DumpSchema.new db, '/tmp/test', pump: lambda{|_| DbPump.new db, nil, codec: :yaml}
|
10
8
|
dumper = DumpSchema.new db, '/tmp/test', pump: ->(dump_schema){ DbPump.new dump_schema.src_db, nil, codec: :yaml}
|
data/wyrm.gemspec
CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.add_runtime_dependency 'sequel'
|
21
|
+
spec.add_runtime_dependency 'sequel'
|
22
22
|
spec.add_runtime_dependency "fastandand"
|
23
23
|
|
24
24
|
spec.add_development_dependency "bundler", "~> 1.3"
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wyrm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Anderson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-08-
|
11
|
+
date: 2013-08-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sequel
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - '>='
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: '0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - '>='
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: fastandand
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|