redis-copy 0.0.5 → 0.0.6
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.
- 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
|