redis-copy 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/.travis.yml +16 -0
- data/.travis/Gemfile.redis-gem-3.0 +6 -0
- data/.travis/Gemfile.redis-gem-3.0.lock +44 -0
- data/.travis/Gemfile.redis-gem-master +6 -0
- data/.travis/Gemfile.redis-gem-master.lock +49 -0
- data/README.md +1 -1
- data/Rakefile +60 -0
- data/lib/redis-copy.rb +6 -1
- data/lib/redis-copy/cli.rb +15 -4
- data/lib/redis-copy/key-emitter.rb +31 -4
- data/lib/redis-copy/strategy/classic.rb +39 -0
- data/lib/redis-copy/version.rb +1 -1
- data/redis-copy.gemspec +4 -5
- data/spec/redis-copy/key-emitter_spec.rb +47 -31
- data/spec/redis-copy/strategy_spec.rb +23 -47
- data/spec/redis.spec.conf +9 -0
- data/spec/spec_helper.rb +38 -0
- metadata +21 -4
data/.gitignore
CHANGED
data/.travis.yml
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
language: ruby
|
2
|
+
script: "bundle exec rake run"
|
3
|
+
|
4
|
+
rvm:
|
5
|
+
- 1.9.3
|
6
|
+
gemfile:
|
7
|
+
- .travis/Gemfile.redis-gem-3.0
|
8
|
+
- .travis/Gemfile.redis-gem-master
|
9
|
+
env:
|
10
|
+
global:
|
11
|
+
- TIMEOUT=1
|
12
|
+
matrix:
|
13
|
+
- REDIS_BRANCH=2.4
|
14
|
+
- REDIS_BRANCH=2.6
|
15
|
+
- REDIS_BRANCH=2.8
|
16
|
+
- REDIS_BRANCH=unstable
|
@@ -0,0 +1,44 @@
|
|
1
|
+
PATH
|
2
|
+
remote: ../
|
3
|
+
specs:
|
4
|
+
redis-copy (0.0.5)
|
5
|
+
activesupport
|
6
|
+
redis
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
activesupport (4.0.1)
|
12
|
+
i18n (~> 0.6, >= 0.6.4)
|
13
|
+
minitest (~> 4.2)
|
14
|
+
multi_json (~> 1.3)
|
15
|
+
thread_safe (~> 0.1)
|
16
|
+
tzinfo (~> 0.3.37)
|
17
|
+
atomic (1.1.14)
|
18
|
+
diff-lcs (1.2.5)
|
19
|
+
i18n (0.6.5)
|
20
|
+
minitest (4.7.5)
|
21
|
+
multi_json (1.8.2)
|
22
|
+
rake (10.1.0)
|
23
|
+
redis (3.0.5)
|
24
|
+
rspec (2.14.1)
|
25
|
+
rspec-core (~> 2.14.0)
|
26
|
+
rspec-expectations (~> 2.14.0)
|
27
|
+
rspec-mocks (~> 2.14.0)
|
28
|
+
rspec-core (2.14.7)
|
29
|
+
rspec-expectations (2.14.4)
|
30
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
31
|
+
rspec-mocks (2.14.4)
|
32
|
+
thread_safe (0.1.3)
|
33
|
+
atomic
|
34
|
+
tzinfo (0.3.38)
|
35
|
+
|
36
|
+
PLATFORMS
|
37
|
+
ruby
|
38
|
+
|
39
|
+
DEPENDENCIES
|
40
|
+
bundler (~> 1.3)
|
41
|
+
rake
|
42
|
+
redis (~> 3.0)
|
43
|
+
redis-copy!
|
44
|
+
rspec (~> 2.14)
|
@@ -0,0 +1,49 @@
|
|
1
|
+
GIT
|
2
|
+
remote: https://github.com/redis/redis-rb.git
|
3
|
+
revision: 9bb4156dcd43105b68e24e5efc579dddb12cf260
|
4
|
+
specs:
|
5
|
+
redis (3.0.6)
|
6
|
+
|
7
|
+
PATH
|
8
|
+
remote: ../
|
9
|
+
specs:
|
10
|
+
redis-copy (0.0.5)
|
11
|
+
activesupport
|
12
|
+
redis
|
13
|
+
|
14
|
+
GEM
|
15
|
+
remote: https://rubygems.org/
|
16
|
+
specs:
|
17
|
+
activesupport (4.0.1)
|
18
|
+
i18n (~> 0.6, >= 0.6.4)
|
19
|
+
minitest (~> 4.2)
|
20
|
+
multi_json (~> 1.3)
|
21
|
+
thread_safe (~> 0.1)
|
22
|
+
tzinfo (~> 0.3.37)
|
23
|
+
atomic (1.1.14)
|
24
|
+
diff-lcs (1.2.5)
|
25
|
+
i18n (0.6.5)
|
26
|
+
minitest (4.7.5)
|
27
|
+
multi_json (1.8.2)
|
28
|
+
rake (10.1.0)
|
29
|
+
rspec (2.14.1)
|
30
|
+
rspec-core (~> 2.14.0)
|
31
|
+
rspec-expectations (~> 2.14.0)
|
32
|
+
rspec-mocks (~> 2.14.0)
|
33
|
+
rspec-core (2.14.7)
|
34
|
+
rspec-expectations (2.14.4)
|
35
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
36
|
+
rspec-mocks (2.14.4)
|
37
|
+
thread_safe (0.1.3)
|
38
|
+
atomic
|
39
|
+
tzinfo (0.3.38)
|
40
|
+
|
41
|
+
PLATFORMS
|
42
|
+
ruby
|
43
|
+
|
44
|
+
DEPENDENCIES
|
45
|
+
bundler (~> 1.3)
|
46
|
+
rake
|
47
|
+
redis!
|
48
|
+
redis-copy!
|
49
|
+
rspec (~> 2.14)
|
data/README.md
CHANGED
@@ -24,7 +24,7 @@ $ redis-copy --help
|
|
24
24
|
redis-copy v0.0.5
|
25
25
|
Usage: redis-copy [options] <source> <destination>
|
26
26
|
<source> and <destination> must be redis connection uris
|
27
|
-
like [redis://]<hostname>[:<port>][/<db>]
|
27
|
+
like [redis://][<username>:<password>@]<hostname>[:<port>][/<db>]
|
28
28
|
|
29
29
|
Specific options:
|
30
30
|
--strategy STRATEGY Select strategy (auto, new, classic) (default auto)
|
data/Rakefile
CHANGED
@@ -7,3 +7,63 @@ RSpec::Core::RakeTask.new(:spec) do |spec|
|
|
7
7
|
spec.pattern = FileList['spec/**/*_spec.rb']
|
8
8
|
spec.verbose = true
|
9
9
|
end
|
10
|
+
|
11
|
+
|
12
|
+
# STOLEN NEARLY VERBATIM FROM MIT-LICENSED REDIS-RB
|
13
|
+
# https://github.com/redis/redis-rb
|
14
|
+
require 'rubygems'
|
15
|
+
|
16
|
+
ENV["REDIS_BRANCH"] ||= "unstable"
|
17
|
+
|
18
|
+
$:.unshift File.join(File.dirname(__FILE__), 'lib')
|
19
|
+
require 'redis/version'
|
20
|
+
|
21
|
+
REDIS_DIR = File.expand_path(File.join("..", "spec"), __FILE__)
|
22
|
+
REDIS_CNF = File.join(REDIS_DIR, "redis.spec.conf")
|
23
|
+
REDIS_PID = File.join(REDIS_DIR, "db", "redis.pid")
|
24
|
+
BINARY = "tmp/redis-#{ENV["REDIS_BRANCH"]}/src/redis-server"
|
25
|
+
|
26
|
+
task :default => :run
|
27
|
+
|
28
|
+
desc "Run tests and manage server start/stop"
|
29
|
+
task :run => [:start, :spec, :stop]
|
30
|
+
|
31
|
+
desc "Start the Redis server"
|
32
|
+
task :start => BINARY do
|
33
|
+
sh "#{BINARY} --version"
|
34
|
+
|
35
|
+
redis_running = \
|
36
|
+
begin
|
37
|
+
File.exists?(REDIS_PID) && Process.kill(0, File.read(REDIS_PID).to_i)
|
38
|
+
rescue Errno::ESRCH
|
39
|
+
FileUtils.rm REDIS_PID
|
40
|
+
false
|
41
|
+
end
|
42
|
+
|
43
|
+
unless redis_running
|
44
|
+
unless system("#{BINARY} #{REDIS_CNF}")
|
45
|
+
abort "could not start redis-server"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
desc "Stop the Redis server"
|
51
|
+
task :stop do
|
52
|
+
if File.exists?(REDIS_PID)
|
53
|
+
Process.kill "INT", File.read(REDIS_PID).to_i
|
54
|
+
FileUtils.rm REDIS_PID
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
file BINARY do
|
59
|
+
branch = ENV.fetch("REDIS_BRANCH")
|
60
|
+
|
61
|
+
sh <<-SH
|
62
|
+
mkdir -p tmp;
|
63
|
+
cd tmp;
|
64
|
+
wget https://github.com/antirez/redis/archive/#{branch}.tar.gz -O #{branch}.tar.gz;
|
65
|
+
tar xf #{branch}.tar.gz;
|
66
|
+
cd redis-#{branch};
|
67
|
+
make
|
68
|
+
SH
|
69
|
+
end
|
data/lib/redis-copy.rb
CHANGED
@@ -93,8 +93,13 @@ module RedisCopy
|
|
93
93
|
host = uri.host
|
94
94
|
port = uri.port if uri.port
|
95
95
|
db = uri.path ? uri.path[1..-1].to_i : 0
|
96
|
+
password = uri.password
|
96
97
|
|
97
|
-
|
98
|
+
# Connect & Ping to ensure access.
|
99
|
+
Redis.new(host: host, port: port, db: db, password: password).tap(&:ping)
|
100
|
+
rescue Redis::CommandError => e
|
101
|
+
fail(Redis::CommandError,
|
102
|
+
"There was a problem connecting to #{uri.to_s}\n#{e.message}")
|
98
103
|
end
|
99
104
|
end
|
100
105
|
end
|
data/lib/redis-copy/cli.rb
CHANGED
@@ -5,10 +5,10 @@ require 'optparse'
|
|
5
5
|
|
6
6
|
module RedisCopy
|
7
7
|
class CLI
|
8
|
-
REDIS_URI = (/\A(?:redis:\/\/)?([a-z0-9\-.]+)(:[0-9]{1,5})?(\/(?:(?:1[0-5])|[0-9]))?\z/i).freeze
|
8
|
+
REDIS_URI = (/\A(?:redis:\/\/)?(\w*:\w+@)?([a-z0-9\-.]+)(:[0-9]{1,5})?(\/(?:(?:1[0-5])|[0-9]))?\z/i).freeze
|
9
9
|
DEFAULTS = {
|
10
10
|
ui: :command_line,
|
11
|
-
key_emitter: :
|
11
|
+
key_emitter: :auto,
|
12
12
|
strategy: :auto,
|
13
13
|
verify: 0,
|
14
14
|
pipeline: :true,
|
@@ -25,7 +25,7 @@ module RedisCopy
|
|
25
25
|
|
26
26
|
OptionParser.new do |opts|
|
27
27
|
opts.version = RedisCopy::VERSION
|
28
|
-
opts.banner = "#{opts.program_name} v#{opts.version}\n" +
|
28
|
+
opts.banner = "#{opts.program_name} v#{opts.version} (with redis-rb #{Redis::VERSION})\n" +
|
29
29
|
"Usage: #{opts.program_name} [options] <source> <destination>"
|
30
30
|
|
31
31
|
indent_desc = proc do |desc|
|
@@ -33,7 +33,7 @@ module RedisCopy
|
|
33
33
|
end
|
34
34
|
|
35
35
|
opts.separator " <source> and <destination> must be redis connection uris"
|
36
|
-
opts.separator " like [redis://]<hostname>[:<port>][/<db>]"
|
36
|
+
opts.separator " like [redis://][<username>:<password>@]<hostname>[:<port>][/<db>]"
|
37
37
|
opts.separator ''
|
38
38
|
opts.separator "Specific options:"
|
39
39
|
|
@@ -48,6 +48,17 @@ module RedisCopy
|
|
48
48
|
options[:strategy] = strategy
|
49
49
|
end
|
50
50
|
|
51
|
+
opts.on('--emitter EMITTER', [:auto, :scan, :keys],
|
52
|
+
indent_desc.(
|
53
|
+
"Select key emitter (auto, keys, scan) (default #{DEFAULTS[:strategy]})\n" +
|
54
|
+
" auto: uses scan if available, otherwise fallback\n" +
|
55
|
+
" scan: use redis SCAN command (faster, less blocking)\n" +
|
56
|
+
" keys: uses redis KEYS command (dangerous, esp. on large datasets)"
|
57
|
+
)
|
58
|
+
) do |emitter|
|
59
|
+
options[:key_emitter] = emitter
|
60
|
+
end
|
61
|
+
|
51
62
|
opts.on('--[no-]pipeline',
|
52
63
|
"Use redis pipeline where available (default #{DEFAULTS[:pipeline]})"
|
53
64
|
) do |pipeline|
|
@@ -9,9 +9,15 @@ module RedisCopy
|
|
9
9
|
module KeyEmitter
|
10
10
|
def self.load(redis, ui, options = {})
|
11
11
|
key_emitter = options.fetch(:key_emitter, :default)
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
scan_compatible = Scan::compatible?(redis)
|
13
|
+
emitklass = case key_emitter
|
14
|
+
when :keys then Keys
|
15
|
+
when :scan
|
16
|
+
raise ArgumentError unless scan_compatible
|
17
|
+
Scan
|
18
|
+
when :auto then scan_compatible ? Scan : Keys
|
19
|
+
end
|
20
|
+
emitklass.new(redis, ui, options)
|
15
21
|
end
|
16
22
|
|
17
23
|
# @param redis [Redis]
|
@@ -37,7 +43,7 @@ module RedisCopy
|
|
37
43
|
end
|
38
44
|
|
39
45
|
# The default strategy blindly uses `redis.keys('*')`
|
40
|
-
class
|
46
|
+
class Keys
|
41
47
|
include KeyEmitter
|
42
48
|
|
43
49
|
def keys
|
@@ -66,6 +72,27 @@ module RedisCopy
|
|
66
72
|
@ui.debug "REDIS: #{@redis.client.id} KEYS *"
|
67
73
|
@redis.keys('*').to_enum
|
68
74
|
end
|
75
|
+
|
76
|
+
def self.compatible?(redis)
|
77
|
+
true
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class Scan
|
82
|
+
include KeyEmitter
|
83
|
+
|
84
|
+
def keys
|
85
|
+
@redis.scan_each(count: 1000)
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.compatible?(redis)
|
89
|
+
bin_version = Gem::Version.new(redis.info['redis_version'])
|
90
|
+
bin_requirement = Gem::Requirement.new('>= 2.7.105')
|
91
|
+
|
92
|
+
return false unless bin_requirement.satisfied_by?(bin_version)
|
93
|
+
|
94
|
+
redis.respond_to?(:scan_each)
|
95
|
+
end
|
69
96
|
end
|
70
97
|
end
|
71
98
|
end
|
@@ -1,5 +1,36 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
+
# The Classic strategy borrows *heavily* from the utils/redis-copy.rb found
|
4
|
+
# in Redis source, which is also the inspiration for this gem.
|
5
|
+
#
|
6
|
+
# #{REDIS}/utils/redis-copy.rb - Copyright © 2009-2010 Salvatore Sanfilippo.
|
7
|
+
# All rights reserved.
|
8
|
+
#
|
9
|
+
# Redistribution and use in source and binary forms, with or without
|
10
|
+
# modification, are permitted provided that the following conditions
|
11
|
+
# are met:
|
12
|
+
#
|
13
|
+
# * Redistributions of source code must retain the above copyright
|
14
|
+
# notice, this list of conditions and the following disclaimer.
|
15
|
+
# * Redistributions in binary form must reproduce the above copyright
|
16
|
+
# notice, this list of conditions and the following disclaimer in
|
17
|
+
# the documentation and/or other materials provided with the
|
18
|
+
# distribution.
|
19
|
+
# * Neither the name of Redis nor the names of its contributors may
|
20
|
+
# be used to endorse or promote products derived from this software
|
21
|
+
# without specific prior written permission.
|
22
|
+
#
|
23
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
24
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
25
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
26
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
27
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
28
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
29
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
30
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
31
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
32
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
33
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
3
34
|
module RedisCopy
|
4
35
|
module Strategy
|
5
36
|
class Classic
|
@@ -51,7 +82,11 @@ module RedisCopy
|
|
51
82
|
vs_zset = @src.zrange(key, 0, -1, :with_scores => true)
|
52
83
|
sv_zset = vs_zset.map(&:reverse)
|
53
84
|
@dst.zadd(key, sv_zset)
|
85
|
+
when 'none'
|
86
|
+
@ui.warn("GONE: #{key.dump}")
|
87
|
+
return false
|
54
88
|
else
|
89
|
+
@ui.warn("UNKNOWN(#{vtype}): #{key.dump}")
|
55
90
|
return false
|
56
91
|
end
|
57
92
|
|
@@ -71,6 +106,10 @@ module RedisCopy
|
|
71
106
|
def pipeline_enabled?
|
72
107
|
@pipeline_enabled ||= (false | @opt[:pipeline])
|
73
108
|
end
|
109
|
+
|
110
|
+
def self.compatible?(redis)
|
111
|
+
true
|
112
|
+
end
|
74
113
|
end
|
75
114
|
end
|
76
115
|
end
|
data/lib/redis-copy/version.rb
CHANGED
data/redis-copy.gemspec
CHANGED
@@ -8,13 +8,12 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.name = 'redis-copy'
|
9
9
|
spec.version = RedisCopy::VERSION
|
10
10
|
|
11
|
-
authors_and_emails =
|
12
|
-
|
13
|
-
|
14
|
-
# end.compact.map(&:to_a)
|
11
|
+
authors_and_emails = (`git shortlog -sne`).lines.map do |l|
|
12
|
+
(/(?<=\t)(.+) <(.+)>\z/).match(l.chomp).to_a.last(2)
|
13
|
+
end.compact.map(&:to_a)
|
15
14
|
|
16
15
|
spec.authors = authors_and_emails.map(&:first)
|
17
|
-
spec.email =
|
16
|
+
spec.email = ['redis-copy@yaauie.com']
|
18
17
|
spec.summary = 'Copy the contents of one redis db to another'
|
19
18
|
spec.description = 'A command-line utility built for copying the ' +
|
20
19
|
'contents of one redis db to another over a ' +
|
@@ -1,51 +1,67 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
require 'redis-copy'
|
3
|
+
require_relative '../spec_helper.rb'
|
3
4
|
|
4
|
-
|
5
|
-
let(:
|
5
|
+
shared_examples_for RedisCopy::KeyEmitter do
|
6
|
+
let(:emitter_klass) { described_class }
|
7
|
+
let(:redis) { Redis.new(REDIS_OPTIONS) }
|
6
8
|
let(:ui) { double.as_null_object }
|
7
|
-
let(:instance) {
|
8
|
-
let(:
|
9
|
-
let(:
|
9
|
+
let(:instance) { emitter_klass.new(redis, ui)}
|
10
|
+
let(:key_count) { 1 }
|
11
|
+
let(:keys) { key_count.times.map{|i| i.to_s(16) } }
|
10
12
|
|
11
13
|
before(:each) do
|
12
|
-
|
13
|
-
|
14
|
+
unless emitter_klass.compatible?(redis)
|
15
|
+
pending "#{emitter_klass} not supported in your environment"
|
16
|
+
end
|
17
|
+
key_count.times.each_slice(50) do |keys|
|
18
|
+
kv = keys.map{|x| x.to_s(16)}.zip(keys)
|
19
|
+
redis.mset(*kv.flatten)
|
20
|
+
end
|
14
21
|
ui.stub(:debug).with(anything)
|
15
22
|
end
|
23
|
+
after(:each) { redis.flushdb }
|
16
24
|
|
17
25
|
context '#keys' do
|
18
|
-
let(:
|
19
|
-
before(:each) do
|
20
|
-
redis.should_receive(:keys).with('*').exactly(:once).and_return(mock_return)
|
21
|
-
end
|
26
|
+
let(:key_count) { 64 }
|
22
27
|
context 'the result' do
|
23
28
|
subject { instance.keys }
|
24
|
-
its(:to_a) { should
|
29
|
+
its(:to_a) { should =~ keys }
|
25
30
|
end
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
confirmation.should match /\b100,000\b/
|
38
|
-
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe RedisCopy::KeyEmitter::Keys do
|
35
|
+
it_should_behave_like RedisCopy::KeyEmitter do
|
36
|
+
context '#keys' do
|
37
|
+
context 'the supplied ui' do
|
38
|
+
it 'should get a debug message' do
|
39
|
+
ui.should_receive(:debug).
|
40
|
+
with(/#{redis.client.id} KEYS \*/).
|
41
|
+
exactly(:once)
|
39
42
|
instance.keys
|
40
43
|
end
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
44
|
+
context 'when source has > 10,000 keys' do
|
45
|
+
let(:key_count) { 10_001 }
|
46
|
+
it 'should ask for confirmation' do
|
47
|
+
ui.should_receive(:confirm?) do |confirmation|
|
48
|
+
confirmation.should match /\b10,001/
|
49
|
+
end
|
50
|
+
instance.keys
|
51
|
+
end
|
52
|
+
end
|
53
|
+
context 'when source has <= 10,000 keys' do
|
54
|
+
let(:key_count) { 1_000 }
|
55
|
+
it 'should not ask for confirmation' do
|
56
|
+
ui.should_not_receive(:confirm?)
|
57
|
+
instance.keys
|
58
|
+
end
|
47
59
|
end
|
48
60
|
end
|
49
61
|
end
|
50
62
|
end
|
51
63
|
end
|
64
|
+
|
65
|
+
describe RedisCopy::KeyEmitter::Scan do
|
66
|
+
it_should_behave_like RedisCopy::KeyEmitter
|
67
|
+
end
|
@@ -1,40 +1,10 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
|
3
|
-
ResponseError = Class.new(RuntimeError)
|
4
|
-
|
5
|
-
def ensure_same!(&blk)
|
6
|
-
responses = {
|
7
|
-
source: capture_result(source, &blk),
|
8
|
-
destination: capture_result(destination, &blk)
|
9
|
-
}
|
10
|
-
unless responses[:source] == responses[:destination]
|
11
|
-
raise ResponseError.new(responses.to_s)
|
12
|
-
end
|
13
|
-
case responses[:destination].first
|
14
|
-
when :raised then raise responses[:destination].last
|
15
|
-
when :returned then return responses[:destination].last
|
16
|
-
end
|
17
|
-
end
|
18
|
-
alias_method :both!, :ensure_same!
|
19
|
-
|
20
|
-
def both(&blk)
|
21
|
-
both!(&blk)
|
22
|
-
true
|
23
|
-
rescue ResponseError
|
24
|
-
false
|
25
|
-
end
|
26
|
-
|
27
|
-
def capture_result(redis, &block)
|
28
|
-
return [:returned, block.call(redis)]
|
29
|
-
rescue Object => exception
|
30
|
-
return [:raised, exception]
|
31
|
-
end
|
32
|
-
end
|
2
|
+
require_relative '../spec_helper'
|
33
3
|
|
34
4
|
shared_examples_for(:no_ttl) do
|
35
5
|
# key, redis,
|
36
6
|
subject { redis.ttl(key) }
|
37
|
-
it { should
|
7
|
+
it { should be < 0 }
|
38
8
|
end
|
39
9
|
|
40
10
|
shared_examples_for(:ttl_set) do
|
@@ -56,10 +26,25 @@ shared_examples_for '#verify?' do
|
|
56
26
|
end
|
57
27
|
|
58
28
|
shared_examples_for(RedisCopy::Strategy) do
|
29
|
+
let(:strategy_class) { described_class }
|
30
|
+
let(:options) { Hash.new } # append using before(:each) { options.update(foo: true) }
|
31
|
+
# let(:ui) { double.as_null_object }
|
32
|
+
let(:ui) { RedisCopy::UI::CommandLine.new(options) }
|
33
|
+
let(:strategy) { strategy_class.new(source, destination, ui, options)}
|
34
|
+
let(:multiplex) { RedisMultiplex.new(source, destination) }
|
35
|
+
let(:source) { Redis.new(REDIS_OPTIONS.merge(db: 14)) }
|
36
|
+
let(:destination) { Redis.new(REDIS_OPTIONS.merge(db: 15)) }
|
37
|
+
|
59
38
|
let(:key) { rand(16**128).to_s(16) }
|
60
39
|
after(:each) { multiplex.both { |redis| redis.del(key) } }
|
61
40
|
let(:ttl) { 100 }
|
62
41
|
|
42
|
+
before(:each) do
|
43
|
+
unless [source, destination].all?{|redis| strategy_class.compatible?(redis) }
|
44
|
+
pending "#{strategy_class} not supported in your environment"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
63
48
|
context '#copy' do
|
64
49
|
before(:each) { populate.call }
|
65
50
|
context 'string' do
|
@@ -301,22 +286,13 @@ shared_examples_for(RedisCopy::Strategy) do
|
|
301
286
|
end
|
302
287
|
end
|
303
288
|
|
304
|
-
describe RedisCopy::Strategy do
|
305
|
-
let(:options) { Hash.new } # append using before(:each) { options.update(foo: true) }
|
306
|
-
# let(:ui) { double.as_null_object }
|
307
|
-
let(:ui) { RedisCopy::UI::CommandLine.new(options) }
|
308
|
-
let(:strategy) { strategy_class.new(source, destination, ui, options)}
|
309
|
-
let(:multiplex) { RedisMultiplex.new(source, destination) }
|
310
|
-
let(:source) { Redis.new(db: 14) }
|
311
|
-
let(:destination) { Redis.new(db: 15) }
|
312
289
|
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
it_should_behave_like RedisCopy::Strategy
|
290
|
+
describe RedisCopy::Strategy::New do
|
291
|
+
it_should_behave_like RedisCopy::Strategy
|
292
|
+
end
|
293
|
+
|
294
|
+
describe RedisCopy::Strategy::Classic do
|
295
|
+
it_should_behave_like RedisCopy::Strategy do
|
320
296
|
context '#maybe_pipeline' do
|
321
297
|
it 'should not pipeline' do
|
322
298
|
source.should_not_receive(:pipelined)
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
REDIS_PORT = 6381
|
2
|
+
REDIS_OPTIONS = {
|
3
|
+
:port => REDIS_PORT,
|
4
|
+
:db => 14,
|
5
|
+
:timeout => Float(ENV["TIMEOUT"] || 0.1),
|
6
|
+
}
|
7
|
+
|
8
|
+
class RedisMultiplex < Struct.new(:source, :destination)
|
9
|
+
ResponseError = Class.new(RuntimeError)
|
10
|
+
|
11
|
+
def ensure_same!(&blk)
|
12
|
+
responses = {
|
13
|
+
source: capture_result(source, &blk),
|
14
|
+
destination: capture_result(destination, &blk)
|
15
|
+
}
|
16
|
+
unless responses[:source] == responses[:destination]
|
17
|
+
raise ResponseError.new(responses.to_s)
|
18
|
+
end
|
19
|
+
case responses[:destination].first
|
20
|
+
when :raised then raise responses[:destination].last
|
21
|
+
when :returned then return responses[:destination].last
|
22
|
+
end
|
23
|
+
end
|
24
|
+
alias_method :both!, :ensure_same!
|
25
|
+
|
26
|
+
def both(&blk)
|
27
|
+
both!(&blk)
|
28
|
+
true
|
29
|
+
rescue ResponseError
|
30
|
+
false
|
31
|
+
end
|
32
|
+
|
33
|
+
def capture_result(redis, &block)
|
34
|
+
return [:returned, block.call(redis)]
|
35
|
+
rescue Object => exception
|
36
|
+
return [:raised, exception]
|
37
|
+
end
|
38
|
+
end
|
metadata
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redis-copy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Ryan Biesemeyer
|
9
|
+
- Denis Goeury
|
9
10
|
autorequire:
|
10
11
|
bindir: bin
|
11
12
|
cert_chain: []
|
12
|
-
date: 2013-11-
|
13
|
+
date: 2013-11-13 00:00:00.000000000 Z
|
13
14
|
dependencies:
|
14
15
|
- !ruby/object:Gem::Dependency
|
15
16
|
name: bundler
|
@@ -95,13 +96,18 @@ description: A command-line utility built for copying the contents of one redis
|
|
95
96
|
to another over a network. Supports all data types, persists ttls, and attempts
|
96
97
|
to be as efficient as possible.
|
97
98
|
email:
|
98
|
-
-
|
99
|
+
- redis-copy@yaauie.com
|
99
100
|
executables:
|
100
101
|
- redis-copy
|
101
102
|
extensions: []
|
102
103
|
extra_rdoc_files: []
|
103
104
|
files:
|
104
105
|
- .gitignore
|
106
|
+
- .travis.yml
|
107
|
+
- .travis/Gemfile.redis-gem-3.0
|
108
|
+
- .travis/Gemfile.redis-gem-3.0.lock
|
109
|
+
- .travis/Gemfile.redis-gem-master
|
110
|
+
- .travis/Gemfile.redis-gem-master.lock
|
105
111
|
- Gemfile
|
106
112
|
- LICENSE.txt
|
107
113
|
- README.md
|
@@ -120,8 +126,11 @@ files:
|
|
120
126
|
- lib/redis-copy/version.rb
|
121
127
|
- redis-copy.gemspec
|
122
128
|
- redis-copy_spec.rb
|
129
|
+
- spec/db/.gitkeep
|
123
130
|
- spec/redis-copy/key-emitter_spec.rb
|
124
131
|
- spec/redis-copy/strategy_spec.rb
|
132
|
+
- spec/redis.spec.conf
|
133
|
+
- spec/spec_helper.rb
|
125
134
|
homepage: https://github.com/yaauie/redis-copy
|
126
135
|
licenses:
|
127
136
|
- MIT
|
@@ -135,12 +144,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
135
144
|
- - ! '>='
|
136
145
|
- !ruby/object:Gem::Version
|
137
146
|
version: '0'
|
147
|
+
segments:
|
148
|
+
- 0
|
149
|
+
hash: 2506344141455451823
|
138
150
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
139
151
|
none: false
|
140
152
|
requirements:
|
141
153
|
- - ! '>='
|
142
154
|
- !ruby/object:Gem::Version
|
143
155
|
version: '0'
|
156
|
+
segments:
|
157
|
+
- 0
|
158
|
+
hash: 2506344141455451823
|
144
159
|
requirements: []
|
145
160
|
rubyforge_project:
|
146
161
|
rubygems_version: 1.8.24
|
@@ -148,6 +163,8 @@ signing_key:
|
|
148
163
|
specification_version: 3
|
149
164
|
summary: Copy the contents of one redis db to another
|
150
165
|
test_files:
|
166
|
+
- spec/db/.gitkeep
|
151
167
|
- spec/redis-copy/key-emitter_spec.rb
|
152
168
|
- spec/redis-copy/strategy_spec.rb
|
153
|
-
|
169
|
+
- spec/redis.spec.conf
|
170
|
+
- spec/spec_helper.rb
|