wyrm 0.2.1 → 0.3.0
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/.gitignore +2 -0
- data/.rvmrc +1 -1
- data/Gemfile +0 -3
- data/README.md +18 -11
- data/bin/wyrm +40 -24
- data/bin/wyrm-view +34 -0
- data/lib/wyrm.rb +3 -5
- data/lib/wyrm/cli.rb +9 -0
- data/lib/wyrm/core_extensions.rb +10 -0
- data/lib/wyrm/{dump_schema.rb → dump.rb} +22 -21
- data/lib/wyrm/hole.rb +164 -0
- data/lib/wyrm/logger.rb +11 -0
- data/lib/wyrm/module.rb +1 -0
- data/lib/wyrm/{db_pump.rb → pump.rb} +40 -34
- data/lib/wyrm/pump_maker.rb +10 -4
- data/lib/wyrm/{restore_schema.rb → restore.rb} +40 -33
- data/lib/wyrm/schema_tools.rb +91 -0
- data/lib/wyrm/version.rb +1 -1
- data/snippets/console.rb +5 -3
- data/spec/core_extensions_spec.rb +50 -0
- data/spec/hole_mouth_spec.rb +176 -0
- data/spec/pump_spec.rb +62 -0
- data/spec/schema_tools_spec.rb +201 -0
- data/wyrm.gemspec +12 -3
- metadata +135 -23
- data/lib/wyrm/other_schema.rb +0 -6
- data/lib/wyrm/transferer.rb +0 -32
data/lib/wyrm/version.rb
CHANGED
data/snippets/console.rb
CHANGED
@@ -3,8 +3,10 @@ require 'sqlite3'
|
|
3
3
|
require 'pathname'
|
4
4
|
require 'wyrm/dump_schema.rb'
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
include Wyrm
|
7
|
+
|
8
|
+
# pump = Wyrm::Pump.new db, :positions, codec: :yaml
|
9
|
+
dumper = DumpSchema.new db, '/tmp/test', pump: lambda{|_| Pump.new db, nil, codec: :yaml}
|
10
|
+
dumper = DumpSchema.new db, '/tmp/test', pump: ->(dump_schema){ Pump.new dump_schema.src_db, nil, codec: :yaml}
|
9
11
|
dumper.dump_tables
|
10
12
|
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
|
3
|
+
require Pathname(__dir__) + '../lib/wyrm/core_extensions.rb'
|
4
|
+
|
5
|
+
describe Method do
|
6
|
+
describe '#kwargs_as_hash' do
|
7
|
+
it 'empty for no keywords' do
|
8
|
+
inst = Class.new do
|
9
|
+
def without_kwargs( one, two )
|
10
|
+
@kwargs = method(__method__).kwargs_as_hash(binding)
|
11
|
+
end
|
12
|
+
end.new
|
13
|
+
inst.without_kwargs :one, :two
|
14
|
+
inst.instance_variable_get('@kwargs').should == {}
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'gives back hash of keywords' do
|
18
|
+
inst = Class.new do
|
19
|
+
def with_kwargs( one: 'one', two: 'two')
|
20
|
+
@kwargs = method(__method__).kwargs_as_hash(binding)
|
21
|
+
end
|
22
|
+
end.new
|
23
|
+
|
24
|
+
inst.with_kwargs
|
25
|
+
inst.instance_variable_get('@kwargs').should == {one: 'one', two: 'two'}
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'has correct values' do
|
29
|
+
inst = Class.new do
|
30
|
+
def with_kwargs( one: 'one', two: 'two')
|
31
|
+
@kwargs = method(__method__).kwargs_as_hash(binding)
|
32
|
+
end
|
33
|
+
end.new
|
34
|
+
|
35
|
+
inst.with_kwargs( one: 1, two: 2 )
|
36
|
+
inst.instance_variable_get('@kwargs').should == {one: 1, two: 2}
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'gets some default values' do
|
40
|
+
inst = Class.new do
|
41
|
+
def with_kwargs( one: 'one', two: 'two')
|
42
|
+
@kwargs = method(__method__).kwargs_as_hash(binding)
|
43
|
+
end
|
44
|
+
end.new
|
45
|
+
|
46
|
+
inst.with_kwargs( one: 1 )
|
47
|
+
inst.instance_variable_get('@kwargs').should == {one: 1, two: 'two'}
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
|
3
|
+
require Pathname(__dir__) + '../lib/wyrm/hole.rb'
|
4
|
+
|
5
|
+
describe Wyrm::Hole::Mouth do
|
6
|
+
describe '#flush' do
|
7
|
+
it 'calls poison_queue' do
|
8
|
+
subject.should_receive(:poison_queue)
|
9
|
+
subject.flush
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'sets flag' do
|
13
|
+
subject.instance_variable_get('@flushed').should_not == true
|
14
|
+
subject.flush
|
15
|
+
subject.instance_variable_get('@flushed').should == true
|
16
|
+
end
|
17
|
+
|
18
|
+
describe 'queue empty with waiters' do
|
19
|
+
THREADS = rand(1..7)
|
20
|
+
def waiters
|
21
|
+
@waiters ||= THREADS.times.map do
|
22
|
+
Thread.new do
|
23
|
+
values = []
|
24
|
+
begin
|
25
|
+
until subject.eof?
|
26
|
+
values << subject.deq
|
27
|
+
sleep( rand * 0.05 )
|
28
|
+
end
|
29
|
+
[:eoffed, values]
|
30
|
+
rescue StopIteration
|
31
|
+
[:poisoned, values]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'poisons queue' do
|
38
|
+
waiters
|
39
|
+
# wait for thread setup to finish
|
40
|
+
sleep 0.1
|
41
|
+
subject.flush
|
42
|
+
thread_values = waiters.map {|waiter| waiter.join(4).andand.value }
|
43
|
+
thread_values.map(&:first).should == THREADS.times.map{:poisoned}
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'eof queue' do
|
47
|
+
50.times{subject.enq 'hello'}
|
48
|
+
waiters
|
49
|
+
subject.flush
|
50
|
+
thread_values = waiters.map {|waiter| waiter.join(4).andand.value }
|
51
|
+
thread_values.map(&:first).should == THREADS.times.map{:eoffed}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '#eof?' do
|
57
|
+
describe 'flushed' do
|
58
|
+
before :each do
|
59
|
+
subject.flush
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'true when queue empty' do
|
63
|
+
subject.queue.should be_empty
|
64
|
+
subject.should be_eof
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'false when queue empty' do
|
68
|
+
subject.enq( :arg )
|
69
|
+
subject.should_not be_eof
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe 'not flushed' do
|
74
|
+
it 'false when queue empty' do
|
75
|
+
subject.queue.should be_empty
|
76
|
+
subject.should_not be_eof
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'false when queue has items' do
|
80
|
+
rand(25).times{ subject.enq( :arg ) }
|
81
|
+
subject.should_not be_eof
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe '#reset' do
|
87
|
+
it 'resets flushed' do
|
88
|
+
subject.instance_variable_set '@flushed', true
|
89
|
+
subject.reset
|
90
|
+
subject.instance_variable_get('@flushed').should == false
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'clears queue' do
|
94
|
+
rand(1..10).times{subject.enq :some_value}
|
95
|
+
subject.queue.should_not be_empty
|
96
|
+
subject.reset
|
97
|
+
subject.queue.should be_empty
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe '#deq' do
|
102
|
+
it 'gets value' do
|
103
|
+
subject.enq :montagne
|
104
|
+
subject.deq.should == :montagne
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'blocks for no values' do
|
108
|
+
subject.queue.should be_empty
|
109
|
+
th = Thread.new{subject.deq}
|
110
|
+
sleep 0.05
|
111
|
+
th.status.should == 'sleep'
|
112
|
+
th.kill
|
113
|
+
sleep 0.05
|
114
|
+
th.status.should == false
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'raise StopIteration on poison' do
|
118
|
+
subject.queue.should be_empty
|
119
|
+
waiter = Thread.new { subject.deq }
|
120
|
+
sleep(0.01) while subject.queue.num_waiting != 1
|
121
|
+
subject.poison_queue
|
122
|
+
|
123
|
+
# this is so cool. Thread#value will re-raise the exception it caught
|
124
|
+
->{waiter.value}.should raise_error(StopIteration)
|
125
|
+
|
126
|
+
# queue should be empty now
|
127
|
+
subject.queue.should be_empty
|
128
|
+
end
|
129
|
+
|
130
|
+
it 're-queues poison' do
|
131
|
+
subject.queue << :poison
|
132
|
+
subject.should_receive(:poison_queue)
|
133
|
+
->{subject.deq}.should raise_error(StopIteration)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
describe '#poison_queue' do
|
138
|
+
it 'poisons when queue empty with waiters' do
|
139
|
+
subject.queue.should be_empty
|
140
|
+
|
141
|
+
# there has to be a thread waiting for the poison to be added
|
142
|
+
waiter = Thread.new { subject.queue.deq }
|
143
|
+
sleep(0.01) while subject.queue.num_waiting != 1
|
144
|
+
subject.poison_queue
|
145
|
+
|
146
|
+
waiter.value.should == :poison
|
147
|
+
|
148
|
+
# queue should be empty now
|
149
|
+
subject.queue.should be_empty
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'no poison when queue empty' do
|
153
|
+
subject.queue.should be_empty
|
154
|
+
subject.poison_queue
|
155
|
+
subject.queue.should be_empty
|
156
|
+
end
|
157
|
+
|
158
|
+
it 'no poison for no waiters' do
|
159
|
+
subject.queue << :hello
|
160
|
+
subject.queue << :there
|
161
|
+
subject.poison_queue
|
162
|
+
subject.queue.size.should == 2
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
describe '#logger' do
|
167
|
+
# this is here because the 2.1.0 without SizeQueue branch
|
168
|
+
# has a logger which nothing else uses
|
169
|
+
it 'works' do
|
170
|
+
Wyrm::Hole::Mouth::RUBY_VERSION = '2.1.0'
|
171
|
+
subject.should_receive(:logger).and_call_original
|
172
|
+
subject.queue
|
173
|
+
Wyrm::Hole::Mouth.send :remove_const, :RUBY_VERSION
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
data/spec/pump_spec.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
|
3
|
+
require Pathname(__dir__) + '../lib/wyrm/pump.rb'
|
4
|
+
|
5
|
+
include Wyrm
|
6
|
+
|
7
|
+
describe Pump do
|
8
|
+
describe '.quacks_like' do
|
9
|
+
it 'recognises method' do
|
10
|
+
threequal = Pump.quacks_like( :tap )
|
11
|
+
(threequal === Object.new).should be_true
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'recognises two methods' do
|
15
|
+
threequal = Pump.quacks_like( :tap, :instance_eval )
|
16
|
+
(threequal === Object.new).should be_true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#table_name=' do
|
21
|
+
it 'invalidates caches'
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#db=' do
|
25
|
+
it 'invalidates caches'
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '#codec=' do
|
29
|
+
it ':yaml' do
|
30
|
+
subject.codec = :yaml
|
31
|
+
subject.codec.should be_a(Pump::YamlCodec)
|
32
|
+
end
|
33
|
+
|
34
|
+
it ':marshal' do
|
35
|
+
subject.codec = :marshal
|
36
|
+
subject.codec.should be_a(Pump::MarshalCodec)
|
37
|
+
end
|
38
|
+
|
39
|
+
def codec_class
|
40
|
+
@codec_class ||=
|
41
|
+
Class.new do
|
42
|
+
def encode; end
|
43
|
+
def decode; end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'codec instance' do
|
48
|
+
inst = codec_class.new
|
49
|
+
subject.codec = inst
|
50
|
+
subject.codec.should == inst
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'codec class' do
|
54
|
+
subject.codec = codec_class
|
55
|
+
subject.codec.should be_a(codec_class)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'raise for unknown' do
|
59
|
+
->{subject.codec = Object.new}.should raise_error(/unknown codec/)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,201 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'sequel'
|
3
|
+
require 'sqlite3'
|
4
|
+
|
5
|
+
require Pathname(__dir__) + '../lib/wyrm/schema_tools.rb'
|
6
|
+
require Pathname(__dir__) + '../lib/wyrm/logger.rb'
|
7
|
+
|
8
|
+
include Wyrm
|
9
|
+
|
10
|
+
describe SchemaTools do
|
11
|
+
after :each do
|
12
|
+
@src_dst = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
class Includer
|
16
|
+
include SchemaTools
|
17
|
+
include Wyrm::Logger
|
18
|
+
def logger
|
19
|
+
lgr = super
|
20
|
+
# silence most logging
|
21
|
+
lgr.level = ::Logger::FATAL
|
22
|
+
lgr
|
23
|
+
end
|
24
|
+
def initialize( src_db, dst_db )
|
25
|
+
@src_db = src_db.andand.extension :schema_dumper
|
26
|
+
@dst_db = dst_db
|
27
|
+
end
|
28
|
+
attr_reader :src_db, :dst_db
|
29
|
+
end
|
30
|
+
|
31
|
+
def src_dst( src_db: Sequel.sqlite, dst_db: Sequel.sqlite )
|
32
|
+
@src_dst ||= Includer.new src_db, dst_db
|
33
|
+
end
|
34
|
+
|
35
|
+
def with_src
|
36
|
+
src_dst dst_db: nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def with_dst
|
40
|
+
src_dst src_db: nil
|
41
|
+
end
|
42
|
+
|
43
|
+
describe '#same_db' do
|
44
|
+
it 'for same db' do
|
45
|
+
src_dst.same_db.should be_true
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'for different db' do
|
49
|
+
src_dst( dst_db: Sequel.postgres ).same_db.should be_false
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe 'src_db dependencies' do
|
54
|
+
describe '#schema_migration' do
|
55
|
+
it 'executes' do
|
56
|
+
String.should === with_src.schema_migration
|
57
|
+
with_src.schema_migration.should =~ /Sequel.migration/
|
58
|
+
with_src.schema_migration.should =~ /change/
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'recreates table' do
|
62
|
+
with_src.src_db.create_table(:things){|t| primary_key :id; t.String :name}
|
63
|
+
with_src.schema_migration.should =~ /create_table/
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe '#index_migration' do
|
68
|
+
it 'executes' do
|
69
|
+
String.should === with_src.index_migration
|
70
|
+
with_src.index_migration.should =~ /Sequel.migration/
|
71
|
+
with_src.index_migration.should =~ /change/
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'recreates table' do
|
75
|
+
with_src.src_db.create_table(:things){|t| primary_key :id; t.String :name}
|
76
|
+
with_src.src_db.add_index(:things, :name)
|
77
|
+
with_src.index_migration.should =~ /add_index/
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe '#fk_migration' do
|
82
|
+
it 'executes' do
|
83
|
+
String.should === with_src.fk_migration
|
84
|
+
with_src.fk_migration.should =~ /Sequel.migration/
|
85
|
+
with_src.fk_migration.should =~ /change/
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'recreates table' do
|
89
|
+
with_src.src_db.create_table(:things){|t| primary_key :id; t.String :name}
|
90
|
+
with_src.src_db.create_table(:times){|t| primary_key :id; t.foreign_key :thing_id, :things}
|
91
|
+
with_src.fk_migration.should =~ /add_foreign_key/
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe '#drop_table_options' do
|
97
|
+
it 'empty for non-postgres' do
|
98
|
+
with_dst.drop_table_options.should == {}
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'cascade for postgres' do
|
102
|
+
src_dst(dst_db: Sequel.postgres, src_db: nil).drop_table_options.should == {cascade: true}
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe '#drop_tables' do
|
107
|
+
#( tables )
|
108
|
+
it 'removes tables with no foreign keys' do
|
109
|
+
with_dst.dst_db.create_table(:things){|t| primary_key :id; t.String :name}
|
110
|
+
with_dst.dst_db.create_table(:times){|t| primary_key :id}
|
111
|
+
|
112
|
+
with_dst.dst_db.tables.should == %i[things times]
|
113
|
+
with_dst.drop_tables with_dst.dst_db.tables
|
114
|
+
with_dst.dst_db.tables.should be_empty
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'removes tables with some foreign keys' do
|
118
|
+
with_dst.dst_db.create_table(:things){|t| primary_key :id; t.String :name}
|
119
|
+
with_dst.dst_db.create_table(:times){|t| primary_key :id; t.foreign_key :thing_id, :things}
|
120
|
+
|
121
|
+
with_dst.dst_db.tables.should == %i[things times]
|
122
|
+
with_dst.drop_tables with_dst.dst_db.tables
|
123
|
+
with_dst.dst_db.tables.should be_empty
|
124
|
+
end
|
125
|
+
|
126
|
+
if ::SQLite3::SQLITE_VERSION >= "3.6.19"
|
127
|
+
it 'sqlite mutual foreign keys' do
|
128
|
+
with_dst.dst_db.create_table(:things){|t| primary_key :id; t.String :name}
|
129
|
+
with_dst.dst_db.create_table(:times){|t| primary_key :id; t.foreign_key :thing_id, :things}
|
130
|
+
with_dst.dst_db.alter_table :things do
|
131
|
+
add_foreign_key :times_id, :times
|
132
|
+
end
|
133
|
+
|
134
|
+
thing_id = with_dst.dst_db[:things].insert name: 'Gruffalo'
|
135
|
+
time_id = with_dst.dst_db[:times].insert thing_id: thing_id
|
136
|
+
with_dst.dst_db[:things].update times_id: time_id
|
137
|
+
|
138
|
+
with_dst.dst_db.tables.sort.should == %i[things times]
|
139
|
+
->{with_dst.drop_tables with_dst.dst_db.tables}.should raise_error(/mutual foreign keys/)
|
140
|
+
end
|
141
|
+
else
|
142
|
+
it "Can't test foreign keys with sqlite < 3.6.19"
|
143
|
+
end
|
144
|
+
|
145
|
+
it 'handles mysql foreign key exception'
|
146
|
+
end
|
147
|
+
|
148
|
+
describe '#create_tables' do
|
149
|
+
MIGRATION = <<-EOF
|
150
|
+
Sequel.migration do
|
151
|
+
change do
|
152
|
+
create_table(:things) do
|
153
|
+
primary_key :id
|
154
|
+
String :name, :size=>255
|
155
|
+
Integer :times_id
|
156
|
+
end
|
157
|
+
|
158
|
+
create_table(:times) do
|
159
|
+
primary_key :id
|
160
|
+
foreign_key :thing_id, :things
|
161
|
+
end
|
162
|
+
|
163
|
+
alter_table(:things) do
|
164
|
+
add_foreign_key [:times_id], :times, :key=>nil
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
EOF
|
169
|
+
|
170
|
+
it 'creates table from schema_migration' do
|
171
|
+
with_dst.stub( :schema_migration ) do
|
172
|
+
MIGRATION
|
173
|
+
end
|
174
|
+
with_dst.dst_db.tables.should be_empty
|
175
|
+
with_dst.create_tables
|
176
|
+
with_dst.dst_db.tables.sort.should == %i[things times]
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# just receive the methods
|
181
|
+
describe '#create_indexes' do
|
182
|
+
it 'creates indexes and foreign keys' do
|
183
|
+
with_dst.should_receive(:index_migration){ 'Sequel.migration{}' }
|
184
|
+
with_dst.should_receive(:fk_migration){ 'Sequel.migration{}' }
|
185
|
+
with_dst.create_indexes
|
186
|
+
end
|
187
|
+
|
188
|
+
it 'resets key sequences for postgres' do
|
189
|
+
with_dst.stub(:index_migration){ 'Sequel.migration{}' }
|
190
|
+
with_dst.stub(:fk_migration){ 'Sequel.migration{}' }
|
191
|
+
|
192
|
+
with_dst.dst_db.stub(:database_type){Sequel.postgres.database_type}
|
193
|
+
|
194
|
+
with_obj = Object.new
|
195
|
+
with_dst.dst_db.should_receive(:tables){[with_obj]}
|
196
|
+
with_dst.dst_db.should_receive(:reset_primary_key_sequence).with(with_obj)
|
197
|
+
with_dst.create_indexes
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
end
|