wyrm 0.2.0 → 0.2.1
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.
- 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
|