redis-copy 0.0.6 → 1.0.0.rc.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.
@@ -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'