wyrm 0.4.1 → 0.4.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.
- checksums.yaml +4 -4
- data/.travis.yml +4 -2
- data/Gemfile +18 -26
- data/History.txt +4 -0
- data/README.md +2 -0
- data/lib/wyrm/dump.rb +100 -75
- data/lib/wyrm/logger.rb +1 -1
- data/lib/wyrm/pump.rb +249 -247
- data/lib/wyrm/pump_maker.rb +23 -22
- data/lib/wyrm/restore.rb +90 -76
- data/lib/wyrm/schema_tools.rb +69 -62
- data/lib/wyrm/version.rb +1 -1
- data/spec/pump_spec.rb +7 -4
- data/spec/rspec_syntax.rb +22 -0
- data/spec/schema_tools_spec.rb +7 -6
- data/wyrm.gemspec +0 -4
- metadata +2 -58
data/lib/wyrm/pump_maker.rb
CHANGED
@@ -1,32 +1,33 @@
|
|
1
1
|
require 'wyrm/pump'
|
2
2
|
require 'wyrm/module'
|
3
3
|
|
4
|
-
module Wyrm
|
5
|
-
|
6
|
-
|
7
|
-
maybe_callable.call
|
8
|
-
|
9
|
-
|
4
|
+
module Wyrm
|
5
|
+
module PumpMaker
|
6
|
+
def call_or_self( maybe_callable )
|
7
|
+
if maybe_callable.respond_to? :call
|
8
|
+
maybe_callable.call( self )
|
9
|
+
else
|
10
|
+
maybe_callable
|
11
|
+
end
|
10
12
|
end
|
11
|
-
end
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
14
|
+
def make_pump( db, pump_thing )
|
15
|
+
call_or_self(pump_thing) || Wyrm::Pump.new( db: db )
|
16
|
+
end
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
18
|
+
def maybe_deebe( db_or_string )
|
19
|
+
case db_or_string
|
20
|
+
when String
|
21
|
+
begin
|
22
|
+
Sequel.connect db_or_string
|
23
|
+
rescue Sequel::AdapterNotFound
|
24
|
+
raise "\nCan't find db driver for #{db_or_string}. It might work to do\n\n gem install #{db_or_string.split(?:).first}\n\n"
|
25
|
+
end
|
26
|
+
when Sequel::Database
|
27
|
+
db_or_string
|
28
|
+
else
|
29
|
+
raise "Don't know how to db-ify #{db_or_string.inspect}"
|
25
30
|
end
|
26
|
-
when Sequel::Database
|
27
|
-
db_or_string
|
28
|
-
else
|
29
|
-
raise "Don't know how to db-ify #{db_or_string.inspect}"
|
30
31
|
end
|
31
32
|
end
|
32
33
|
end
|
data/lib/wyrm/restore.rb
CHANGED
@@ -8,105 +8,119 @@ require 'wyrm/schema_tools'
|
|
8
8
|
|
9
9
|
# Load a schema from a set of dump files (from DumpSchema)
|
10
10
|
# and restore the table data.
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
11
|
+
#
|
12
|
+
# Restore["postgres://localhost:5454/lots", '/var/data/lots']
|
13
|
+
#
|
14
14
|
# TODO the problem with lazy loading the schema files is that
|
15
15
|
# errors in indexes and foreign keys will only be picked up at the
|
16
16
|
# end of they probably lengthy table restore process.
|
17
17
|
# TODO check if table has been restored already, and has the correct rows,
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
module Wyrm
|
19
|
+
class Restore
|
20
|
+
include PumpMaker
|
21
|
+
include SchemaTools
|
22
|
+
include Logger
|
23
|
+
|
24
|
+
def self.[]( *args )
|
25
|
+
new(*args).call
|
26
|
+
end
|
22
27
|
|
23
|
-
|
24
|
-
|
25
|
-
|
28
|
+
def call
|
29
|
+
drop_tables(table_names) if options.drop_tables
|
30
|
+
create_tables
|
31
|
+
restore_tables
|
32
|
+
create_indexes
|
33
|
+
end
|
26
34
|
|
27
|
-
|
28
|
-
|
35
|
+
def initialize( container, dst_db, pump: nil, drop_tables: false )
|
36
|
+
@container = Pathname.new container
|
37
|
+
fail "#{@container} does not exist" unless @container.exist?
|
29
38
|
|
30
|
-
|
31
|
-
|
39
|
+
@dst_db = maybe_deebe dst_db
|
40
|
+
@pump = make_pump( @dst_db, pump )
|
32
41
|
|
33
|
-
|
34
|
-
|
35
|
-
attr_reader :container
|
42
|
+
options.drop_tables = drop_tables
|
43
|
+
end
|
36
44
|
|
37
|
-
|
38
|
-
|
39
|
-
|
45
|
+
attr_reader :pump
|
46
|
+
attr_reader :dst_db
|
47
|
+
attr_reader :container
|
40
48
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
raise "no candidates for #{glob}. Probably #{container} does not have wyrm files." unless candidates.size == 1
|
45
|
-
raise "too many #{candidates.inspect} for #{glob}" unless candidates.size == 1
|
46
|
-
candidates.first
|
47
|
-
end
|
49
|
+
def options
|
50
|
+
@options ||= OpenStruct.new
|
51
|
+
end
|
48
52
|
|
49
|
-
|
50
|
-
@schema_migration ||= find_single( '*schema.rb' ).read
|
51
|
-
end
|
53
|
+
class None < RuntimeError; end
|
52
54
|
|
53
|
-
|
54
|
-
|
55
|
-
|
55
|
+
# sequel wants migrations numbered, but it's a bit of an annoyance for this.
|
56
|
+
def find_single( glob )
|
57
|
+
candidates = Pathname.glob container + glob
|
58
|
+
raise None, "No restore files found for #{glob}" if candidates.size == 0
|
59
|
+
raise "too many #{candidates.inspect} for #{glob}" if candidates.size > 1
|
60
|
+
candidates.first
|
61
|
+
end
|
56
62
|
|
57
|
-
|
58
|
-
|
59
|
-
|
63
|
+
def schema_migration
|
64
|
+
@schema_migration ||= find_single( '*schema.rb' ).read
|
65
|
+
rescue None
|
66
|
+
''
|
67
|
+
end
|
60
68
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
69
|
+
def index_migration
|
70
|
+
@index_migration ||= find_single( '*indexes.rb' ).read
|
71
|
+
rescue None
|
72
|
+
''
|
73
|
+
end
|
66
74
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
open_bz2 table_file do |io|
|
72
|
-
pump.io = io
|
73
|
-
pump.restore filename: table_file
|
75
|
+
def fk_migration
|
76
|
+
@fk_migration ||= find_single( '*foreign_keys.rb' ).read
|
77
|
+
rescue None
|
78
|
+
''
|
74
79
|
end
|
75
|
-
end
|
76
80
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
when Symbol
|
82
|
-
container + "#{table_name}.dbp.bz2"
|
83
|
-
when Pathname
|
84
|
-
table_name
|
85
|
-
else
|
86
|
-
raise "Don't know what to do with #{table_name.inspect}"
|
81
|
+
def reload_migrations
|
82
|
+
@fk_migration = nil
|
83
|
+
@index_migration = nil
|
84
|
+
@schema_migration = nil
|
87
85
|
end
|
88
86
|
|
89
|
-
|
90
|
-
|
87
|
+
# assume the table name is the base name of table_file pathname
|
88
|
+
def restore_table( table_file )
|
89
|
+
logger.info "restoring from #{table_file}"
|
90
|
+
pump.table_name = table_file.basename.sub_ext('').sub_ext('').to_s.to_sym
|
91
|
+
open_bz2 table_file do |io|
|
92
|
+
pump.io = io
|
93
|
+
pump.restore filename: table_file
|
94
|
+
end
|
95
|
+
end
|
91
96
|
|
92
|
-
|
93
|
-
|
94
|
-
|
97
|
+
# open a dbp.bz2 file and either yield or return an io of the uncompressed contents
|
98
|
+
def open_bz2( table_name, &block )
|
99
|
+
table_file =
|
100
|
+
case table_name
|
101
|
+
when Symbol
|
102
|
+
container + "#{table_name}.dbp.bz2"
|
103
|
+
when Pathname
|
104
|
+
table_name
|
105
|
+
else
|
106
|
+
raise "Don't know what to do with #{table_name.inspect}"
|
107
|
+
end
|
108
|
+
|
109
|
+
IO.popen "#{STREAM_DCMP} #{table_file}", &block
|
110
|
+
end
|
95
111
|
|
96
|
-
|
97
|
-
|
98
|
-
|
112
|
+
def table_files
|
113
|
+
Pathname.glob container + '*.dbp.bz2'
|
114
|
+
end
|
99
115
|
|
100
|
-
|
101
|
-
|
102
|
-
path.basename.to_s.split(?.)[0...-2].last.to_sym
|
116
|
+
def restore_tables
|
117
|
+
table_files.sort_by{|tf| tf.stat.size}.each{|table_file| restore_table table_file}
|
103
118
|
end
|
104
|
-
end
|
105
119
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
120
|
+
def table_names
|
121
|
+
table_files.map do |path|
|
122
|
+
path.basename.to_s.split(?.)[0...-2].last.to_sym
|
123
|
+
end
|
124
|
+
end
|
111
125
|
end
|
112
126
|
end
|
data/lib/wyrm/schema_tools.rb
CHANGED
@@ -4,86 +4,93 @@ require 'wyrm/module'
|
|
4
4
|
# needs dst_db for mutate operations
|
5
5
|
# and src_db for fetch operations
|
6
6
|
# src_db must have extension(:schema_dumper)
|
7
|
-
module Wyrm
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
module Wyrm
|
8
|
+
module SchemaTools
|
9
|
+
# some includers will need to provide a different implementation for this.
|
10
|
+
def same_db
|
11
|
+
respond_to?( :dst_db ) && respond_to?( :src_db ) && dst_db&.database_type == src_db&.database_type
|
12
|
+
end
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
14
|
+
def schema_migration
|
15
|
+
@schema_migration ||= src_db.dump_schema_migration indexes: false, :same_db => same_db
|
16
|
+
end
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
18
|
+
# dump single table including indexes, but ignore foreign keys
|
19
|
+
def table_migration( table )
|
20
|
+
src_db.dump_table_schema table, indexes: true, :same_db => same_db
|
21
|
+
end
|
20
22
|
|
21
|
-
|
22
|
-
|
23
|
-
|
23
|
+
def index_migration
|
24
|
+
@index_migration ||= src_db.dump_indexes_migration :same_db => same_db
|
25
|
+
end
|
24
26
|
|
25
|
-
|
26
|
-
|
27
|
-
begin
|
28
|
-
if dst_db.database_type == :postgres
|
29
|
-
{cascade: true}
|
30
|
-
else
|
31
|
-
{}
|
32
|
-
end
|
27
|
+
def fk_migration
|
28
|
+
@fk_migration ||= src_db.dump_foreign_key_migration :same_db => same_db
|
33
29
|
end
|
34
|
-
end
|
35
30
|
|
36
|
-
|
37
|
-
|
38
|
-
# This implementation will fail for tables with mutual foreign keys.
|
39
|
-
# TODO maybe this should use the schema down migration?
|
40
|
-
def drop_tables( tables )
|
41
|
-
foreign_keyed_tables = []
|
42
|
-
tables.each do |table_name|
|
31
|
+
def drop_table_options
|
32
|
+
@drop_table_options ||=
|
43
33
|
begin
|
44
|
-
|
45
|
-
|
34
|
+
if dst_db.database_type == :postgres
|
35
|
+
{cascade: true}
|
36
|
+
else
|
37
|
+
{}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
46
41
|
|
47
|
-
|
48
|
-
|
42
|
+
# Delete given tables.
|
43
|
+
# Recurse if there are foreign keys preventing table deletion.
|
44
|
+
# This implementation will fail for tables with mutual foreign keys.
|
45
|
+
# TODO maybe this should use the schema down migration?
|
46
|
+
def drop_tables( tables )
|
47
|
+
foreign_keyed_tables = []
|
48
|
+
tables.each do |table_name|
|
49
|
+
begin
|
50
|
+
logger.debug "dropping #{table_name}"
|
51
|
+
dst_db.drop_table? table_name, drop_table_options
|
49
52
|
|
50
|
-
|
51
|
-
# Mysql2::Error: Cannot delete or update a parent row: a foreign key constraint fails
|
52
|
-
# SQLite3::ConstraintException: FOREIGN KEY constraint failed==
|
53
|
-
if ex.message =~ /foreign key constraint fail/i
|
53
|
+
rescue Sequel::ForeignKeyConstraintViolation => ex
|
54
54
|
foreign_keyed_tables << table_name
|
55
|
-
|
56
|
-
|
55
|
+
|
56
|
+
rescue Sequel::DatabaseError => ex
|
57
|
+
# Mysql2::Error: Cannot delete or update a parent row: a foreign key constraint fails
|
58
|
+
# SQLite3::ConstraintException: FOREIGN KEY constraint failed==
|
59
|
+
if ex.message =~ /foreign key constraint fail/i
|
60
|
+
foreign_keyed_tables << table_name
|
61
|
+
else
|
62
|
+
raise
|
63
|
+
end
|
57
64
|
end
|
58
65
|
end
|
59
|
-
end
|
60
66
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
67
|
+
# this should be temporary
|
68
|
+
if tables.any? && tables.sort == foreign_keyed_tables.sort
|
69
|
+
raise "can't remove #{tables.inspect} because they have mutual foreign keys"
|
70
|
+
end
|
65
71
|
|
66
|
-
|
67
|
-
|
68
|
-
|
72
|
+
# recursively delete tables. Shuffle as kak workaround for dependency loops.
|
73
|
+
drop_tables foreign_keyed_tables.shuffle unless foreign_keyed_tables.empty?
|
74
|
+
end
|
69
75
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
76
|
+
def create_tables
|
77
|
+
logger.info "creating tables"
|
78
|
+
eval( schema_migration ).apply dst_db, :up
|
79
|
+
end
|
74
80
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
81
|
+
def create_indexes
|
82
|
+
# create indexes and foreign keys, and reset sequences
|
83
|
+
logger.info "creating indexes"
|
84
|
+
eval( index_migration ).apply dst_db, :up
|
79
85
|
|
80
|
-
|
81
|
-
|
86
|
+
logger.info "creating foreign keys"
|
87
|
+
eval( fk_migration ).apply dst_db, :up
|
82
88
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
89
|
+
if dst_db.database_type == :postgres
|
90
|
+
logger.info "reset primary key sequences"
|
91
|
+
dst_db.tables.each{|t| dst_db.reset_primary_key_sequence(t)}
|
92
|
+
logger.info "Primary key sequences reset successfully"
|
93
|
+
end
|
87
94
|
end
|
88
95
|
end
|
89
96
|
end
|
data/lib/wyrm/version.rb
CHANGED
data/spec/pump_spec.rb
CHANGED
@@ -5,6 +5,8 @@ require Pathname(__dir__) + '../lib/wyrm/pump.rb'
|
|
5
5
|
include Wyrm
|
6
6
|
|
7
7
|
describe Pump do
|
8
|
+
include DbConnections
|
9
|
+
|
8
10
|
describe '.quacks_like' do
|
9
11
|
it 'recognises method' do
|
10
12
|
threequal = Pump.quacks_like( :tap )
|
@@ -27,7 +29,7 @@ describe Pump do
|
|
27
29
|
describe '#db=' do
|
28
30
|
it 'invalidates caches' do
|
29
31
|
subject.should_receive(:invalidate_cached_members)
|
30
|
-
subject.db =
|
32
|
+
subject.db = sequel_sqlite_db
|
31
33
|
end
|
32
34
|
|
33
35
|
it 'handles nil db' do
|
@@ -35,20 +37,21 @@ describe Pump do
|
|
35
37
|
end
|
36
38
|
|
37
39
|
it 'adds pagination extension' do
|
38
|
-
db =
|
40
|
+
db = sequel_sqlite_db
|
39
41
|
db.should_receive(:extension).with(:pagination)
|
40
42
|
subject.db = db
|
41
43
|
end
|
42
44
|
|
43
45
|
it 'turns on streaming for postgres' do
|
44
|
-
db =
|
46
|
+
db = sequel_postgres_db
|
47
|
+
pending "Sequel::Postgres::Database not defined" unless defined?(Sequel::Postgres::Database)
|
45
48
|
db.should_receive(:extension).with(:pagination)
|
46
49
|
db.should_receive(:extension).with(:pg_streaming)
|
47
50
|
subject.db = db
|
48
51
|
end
|
49
52
|
|
50
53
|
it 'no streaming for non-postgres' do
|
51
|
-
db =
|
54
|
+
db = sequel_sqlite_db
|
52
55
|
db.should_receive(:extension).with(:pagination)
|
53
56
|
db.should_not_receive(:extension).with(:pg_streaming)
|
54
57
|
subject.db = db
|