redis-copy 0.0.6 → 1.0.0.rc.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ # encoding: utf-8
2
+
3
+ module RedisCopy
4
+ # Scan uses the SCAN family of commands, which were introduced in
5
+ # the 2.8 branch of Redis, and after 3.0.5 of the redis-rb gem.
6
+ class KeyEmitter::Scan
7
+ implements KeyEmitter do |redis, *_|
8
+ bin_version = Gem::Version.new(redis.info['redis_version'])
9
+ bin_requirement = Gem::Requirement.new('>= 2.7.105')
10
+
11
+ next false unless bin_requirement.satisfied_by?(bin_version)
12
+
13
+ redis.respond_to?(:scan_each)
14
+ end
15
+
16
+ def keys
17
+ @redis.scan_each(count: 1000, match: pattern)
18
+ end
19
+ end
20
+ end
@@ -1,29 +1,8 @@
1
1
  # encoding: utf-8
2
2
 
3
- require_relative 'strategy/new'
4
- require_relative 'strategy/classic'
5
-
6
3
  module RedisCopy
7
4
  module Strategy
8
- # @param source [Redis]
9
- # @param destination [Redis]
10
- def self.load(source, destination, ui, options = {})
11
- strategy = options.fetch(:strategy, :auto).to_sym
12
- new_compatible = [source, destination].all?(&New.method(:compatible?))
13
- copierklass = case strategy
14
- when :classic then Classic
15
- when :new
16
- raise ArgumentError unless new_compatible
17
- New
18
- when :auto
19
- new_compatible ? New : Classic
20
- end
21
- copierklass.new(source, destination, ui, options)
22
- end
23
-
24
- def self.included(base)
25
- base.send(:include, Verifier)
26
- end
5
+ extend Implements::Interface
27
6
 
28
7
  # @param source [Redis]
29
8
  # @param destination [Redis]
@@ -35,7 +14,7 @@ module RedisCopy
35
14
  end
36
15
 
37
16
  def to_s
38
- self.class.name.demodulize.humanize
17
+ self.class.name.demodulize
39
18
  end
40
19
 
41
20
  # @param key [String]
@@ -87,3 +66,6 @@ module RedisCopy
87
66
  end
88
67
  end
89
68
  end
69
+
70
+ require_relative 'strategy/classic'
71
+ require_relative 'strategy/dump-restore'
@@ -34,7 +34,7 @@
34
34
  module RedisCopy
35
35
  module Strategy
36
36
  class Classic
37
- include Strategy
37
+ implements Strategy
38
38
 
39
39
  def copy(key)
40
40
  @ui.debug("COPY: #{key.dump}")
@@ -106,10 +106,6 @@ module RedisCopy
106
106
  def pipeline_enabled?
107
107
  @pipeline_enabled ||= (false | @opt[:pipeline])
108
108
  end
109
-
110
- def self.compatible?(redis)
111
- true
112
- end
113
109
  end
114
110
  end
115
111
  end
@@ -2,8 +2,17 @@
2
2
 
3
3
  module RedisCopy
4
4
  module Strategy
5
- class New
6
- include Strategy
5
+ class DumpRestore
6
+ implements Strategy do |source, destination, *_|
7
+ [source, destination].all? do |redis|
8
+ bin_version = Gem::Version.new(redis.info['redis_version'])
9
+ bin_requirement = Gem::Requirement.new('>= 2.6.0')
10
+
11
+ next false unless bin_requirement.satisfied_by?(bin_version)
12
+
13
+ true
14
+ end
15
+ end
7
16
 
8
17
  def copy(key)
9
18
  @ui.debug("COPY: #{key.dump}")
@@ -21,14 +30,6 @@ module RedisCopy
21
30
  @ui.debug("ERROR: #{error}")
22
31
  return false
23
32
  end
24
-
25
- def self.compatible?(redis)
26
- maj, min, *_ = redis.info['redis_version'].split('.').map(&:to_i)
27
- return false unless maj >= 2
28
- return false unless min >= 6
29
-
30
- return true
31
- end
32
33
  end
33
34
  end
34
35
  end
@@ -0,0 +1,299 @@
1
+ # encoding: utf-8
2
+
3
+ # The shared examples for RedisCopy::Strategy are available to require
4
+ # into consuming libraries so they can verify their implementation of the
5
+ # RedisCopy::Strategy interface. See the bundled specs for the bundled
6
+ # key-emitters for example usage.
7
+ if defined?(::RSpec)
8
+ shared_examples_for(RedisCopy::Strategy) do
9
+ let(:strategy_class) { described_class }
10
+ let(:options) { Hash.new } # append using before(:each) { options.update(foo: true) }
11
+ let(:ui) { RedisCopy::UI::CommandLine.new(options) }
12
+ let(:selector) { strategy_class.name.underscore.dasherize } # see implements gem
13
+ let(:strategy_class_finder) { RedisCopy::Strategy.implementation(selector) }
14
+ let(:strategy) { strategy_class_finder.new(source, destination, ui, options) }
15
+ let(:multiplex) { RedisMultiplex.new(source, destination) }
16
+ let(:source) { Redis.new(REDIS_OPTIONS.merge(db: 14)) }
17
+ let(:destination) { Redis.new(REDIS_OPTIONS.merge(db: 15)) }
18
+
19
+ let(:key) { rand(16**128).to_s(16) }
20
+ after(:each) { multiplex.both { |redis| redis.del(key) } }
21
+ let(:ttl) { 100 }
22
+
23
+ before(:each) do
24
+ begin
25
+ strategy.class.should eq strategy_class
26
+ rescue Implements::Implementation::NotFound
27
+ pending "#{strategy_class} not supported in your environment"
28
+ end
29
+ end
30
+
31
+ context '#copy' do
32
+ before(:each) { populate.call }
33
+ context 'string' do
34
+ let(:source_string) { rand(16**256).to_s(16) }
35
+ let(:populate) { proc {source.set(key, source_string)} }
36
+ [true,false].each do |with_expiry|
37
+ context "with_expiry(#{with_expiry})" do
38
+ before(:each) { source.expire(key, ttl) } if with_expiry
39
+ context 'before' do
40
+ context 'source' do
41
+ let(:redis) { source }
42
+ subject { source.get(key) }
43
+ it { should_not be_nil }
44
+ it { should eq source_string }
45
+ it_should_behave_like (with_expiry ? :ttl_set : :no_ttl)
46
+ end
47
+ context 'destination' do
48
+ let(:redis) { destination }
49
+ subject { destination.get(key) }
50
+ it { should be_nil }
51
+ it_should_behave_like :no_ttl
52
+ end
53
+ end
54
+
55
+ context 'after' do
56
+ before(:each) { strategy.copy(key) }
57
+ context 'source' do
58
+ let(:redis) { source }
59
+ subject { source.get(key) }
60
+ it { should_not be_nil }
61
+ it { should eq source_string }
62
+ it_should_behave_like (with_expiry ? :ttl_set : :no_ttl)
63
+ end
64
+ context 'destination' do
65
+ let(:redis) { destination }
66
+ subject { destination.get(key) }
67
+ it { should_not be_nil }
68
+ it { should eq source_string }
69
+ it_should_behave_like (with_expiry ? :ttl_set : :no_ttl)
70
+ end
71
+ it_should_behave_like '#verify?'
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ context 'list' do
78
+ let(:source_list) do
79
+ %w(foo bar baz buz bingo jango)
80
+ end
81
+ let(:populate) { proc { source_list.each{|x| source.rpush(key, x)} } }
82
+ [true,false].each do |with_expiry|
83
+ context "with_expiry(#{with_expiry})" do
84
+ before(:each) { source.expire(key, 100) } if with_expiry
85
+ context 'before' do
86
+ context 'source' do
87
+ let(:redis) { source }
88
+ subject { source.lrange(key, 0, -1) }
89
+ it { should_not be_empty }
90
+ it { should eq source_list }
91
+ it_should_behave_like (with_expiry ? :ttl_set : :no_ttl)
92
+ end
93
+ context 'destination' do
94
+ let(:redis) { destination }
95
+ subject { destination.lrange(key, 0, -1) }
96
+ it { should be_empty }
97
+ it_should_behave_like :no_ttl
98
+ end
99
+ end
100
+
101
+ context 'after' do
102
+ before(:each) { strategy.copy(key) }
103
+ context 'source' do
104
+ let(:redis) { source }
105
+ subject { source.lrange(key, 0, -1) }
106
+ it { should_not be_empty }
107
+ it { should eq source_list }
108
+ it_should_behave_like (with_expiry ? :ttl_set : :no_ttl)
109
+ end
110
+ context 'destination' do
111
+ let(:redis) { destination }
112
+ subject { destination.lrange(key, 0, -1) }
113
+ it { should_not be_empty }
114
+ it { should eq source_list }
115
+ it_should_behave_like (with_expiry ? :ttl_set : :no_ttl)
116
+ end
117
+ it_should_behave_like '#verify?'
118
+ end
119
+ end
120
+ end
121
+ end
122
+
123
+ context 'set' do
124
+ let(:source_list) do
125
+ %w(foo bar baz buz bingo jango)
126
+ end
127
+ let(:populate) { proc { source_list.each{|x| source.sadd(key, x)} } }
128
+ [true,false].each do |with_expiry|
129
+ context "with_expiry(#{with_expiry})" do
130
+ before(:each) { source.expire(key, 100) } if with_expiry
131
+ context 'before' do
132
+ context 'source' do
133
+ let(:redis) { source }
134
+ subject { source.smembers(key) }
135
+ it { should_not be_empty }
136
+ it { should =~ source_list }
137
+ it_should_behave_like (with_expiry ? :ttl_set : :no_ttl)
138
+ end
139
+ context 'destination' do
140
+ let(:redis) { destination }
141
+ subject { destination.smembers(key) }
142
+ it { should be_empty }
143
+ it_should_behave_like :no_ttl
144
+ end
145
+ end
146
+
147
+ context 'after' do
148
+ before(:each) { strategy.copy(key) }
149
+ context 'source' do
150
+ let(:redis) { source }
151
+ subject { source.smembers(key) }
152
+ it { should_not be_empty }
153
+ it { should =~ source_list }
154
+ it_should_behave_like (with_expiry ? :ttl_set : :no_ttl)
155
+ end
156
+ context 'destination' do
157
+ let(:redis) { destination }
158
+ subject { destination.smembers(key) }
159
+ it { should_not be_empty }
160
+ it { should =~ source_list }
161
+ it_should_behave_like (with_expiry ? :ttl_set : :no_ttl)
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
167
+
168
+ context 'hash' do
169
+ let(:source_hash) do
170
+ {
171
+ 'foo' => 'bar',
172
+ 'baz' => 'buz'
173
+ }
174
+ end
175
+ let(:populate) { proc { source.mapped_hmset(key, source_hash) } }
176
+ [true,false].each do |with_expiry|
177
+ context "with_expiry(#{with_expiry})" do
178
+ before(:each) { source.expire(key, 100) } if with_expiry
179
+ context 'before' do
180
+ context 'source' do
181
+ let(:redis) { source }
182
+ subject { source.hgetall(key) }
183
+ it { should_not be_empty }
184
+ it { should eq source_hash }
185
+ it_should_behave_like (with_expiry ? :ttl_set : :no_ttl)
186
+ end
187
+ context 'destination' do
188
+ let(:redis) { destination }
189
+ subject { destination.hgetall(key) }
190
+ it { should be_empty }
191
+ it_should_behave_like :no_ttl
192
+ end
193
+ end
194
+
195
+ context 'after' do
196
+ before(:each) { strategy.copy(key) }
197
+ context 'source' do
198
+ let(:redis) { source }
199
+ subject { source.hgetall(key) }
200
+ it { should_not be_empty }
201
+ it { should eq source_hash }
202
+ it_should_behave_like (with_expiry ? :ttl_set : :no_ttl)
203
+ end
204
+ context 'destination' do
205
+ let(:redis) { destination }
206
+ subject { destination.hgetall(key) }
207
+ it { should_not be_empty }
208
+ it { should eq source_hash }
209
+ it_should_behave_like (with_expiry ? :ttl_set : :no_ttl)
210
+ end
211
+ it_should_behave_like '#verify?'
212
+ end
213
+ end
214
+ end
215
+ end
216
+
217
+ context 'zset' do
218
+ let(:source_zset) do
219
+ {
220
+ 'foo' => 1.0,
221
+ 'baz' => 2.5,
222
+ 'bar' => 1.1,
223
+ 'buz' => 2.7
224
+ }
225
+ end
226
+ let(:vs_source_zset) { source_zset.to_a }
227
+ let(:sv_source_zset) { vs_source_zset.map(&:reverse) }
228
+ let(:populate) { proc { source.zadd(key, sv_source_zset) } }
229
+ [true,false].each do |with_expiry|
230
+ context "with_expiry(#{with_expiry})" do
231
+ before(:each) { source.expire(key, 100) } if with_expiry
232
+ context 'before' do
233
+ context 'source' do
234
+ let(:redis) { source }
235
+ subject { source.zrange(key, 0, -1, :with_scores => true) }
236
+ it { should_not be_empty }
237
+ it { should =~ vs_source_zset }
238
+ it_should_behave_like (with_expiry ? :ttl_set : :no_ttl)
239
+ end
240
+ context 'destination' do
241
+ let(:redis) { destination }
242
+ subject { destination.zrange(key, 0, -1, :with_scores => true) }
243
+ it { should be_empty }
244
+ it_should_behave_like :no_ttl
245
+ end
246
+ end
247
+
248
+ context 'after' do
249
+ before(:each) { strategy.copy(key) }
250
+ context 'source' do
251
+ let(:redis) { source }
252
+ subject { source.zrange(key, 0, -1, :with_scores => true) }
253
+ it { should_not be_empty }
254
+ it { should =~ vs_source_zset }
255
+ it_should_behave_like (with_expiry ? :ttl_set : :no_ttl)
256
+ end
257
+ context 'destination' do
258
+ let(:redis) { destination }
259
+ subject { destination.zrange(key, 0, -1, :with_scores => true) }
260
+ it { should_not be_empty }
261
+ it { should =~ vs_source_zset }
262
+ it_should_behave_like (with_expiry ? :ttl_set : :no_ttl)
263
+ end
264
+ it_should_behave_like '#verify?'
265
+ end
266
+ end
267
+ end
268
+ end
269
+ end
270
+ end
271
+
272
+ shared_examples_for(:no_ttl) do
273
+ # key, redis,
274
+ subject { redis.ttl(key) }
275
+ it { should be < 0 }
276
+ end
277
+
278
+ shared_examples_for(:ttl_set) do
279
+ # key, redis, ttl
280
+ subject { redis.ttl(key) }
281
+ it { should be_within(1).of(ttl) }
282
+ end
283
+
284
+ shared_examples_for '#verify?' do
285
+ before(:each) do
286
+ ui.stub(:debug).and_call_original
287
+ ui.stub(:notify) do |message|
288
+ puts message
289
+ end
290
+ end
291
+ it 'should verify successfully' do
292
+ strategy.verify?(key).should be_true
293
+ end
294
+ end
295
+ else
296
+ fail(LoadError,
297
+ "#{__FILE__} contains shared examples for RedisCopy::Strategy. " +
298
+ "Require it in your specs, not your code.")
299
+ end
data/lib/redis-copy/ui.rb CHANGED
@@ -1,16 +1,8 @@
1
1
  # encoding: utf-8
2
2
 
3
- require_relative 'ui/auto_run'
4
- require_relative 'ui/command_line'
5
-
6
3
  module RedisCopy
7
4
  module UI
8
- def self.load(options = {})
9
- ui = options.fetch(:ui, :auto_run)
10
- const_name = ui.to_s.camelize
11
- require "redis-copy/ui/#{ui}" unless const_defined?(const_name)
12
- const_get(const_name).new(options)
13
- end
5
+ extend Implements::Interface
14
6
 
15
7
  def initialize(options)
16
8
  @options = options
@@ -36,3 +28,7 @@ module RedisCopy
36
28
  end
37
29
  end
38
30
  end
31
+
32
+ # load the bundled uis:
33
+ require_relative 'ui/auto_run'
34
+ require_relative 'ui/command_line'