riak-client 1.0.0.beta → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -4
- data/Gemfile +12 -17
- data/Guardfile +1 -1
- data/LICENSE +16 -0
- data/README.markdown +178 -0
- data/RELEASE_NOTES.md +99 -0
- data/Rakefile +25 -1
- data/erl_src/riak_kv_test014_backend.beam +0 -0
- data/erl_src/riak_kv_test014_backend.erl +189 -0
- data/erl_src/riak_kv_test_backend.beam +0 -0
- data/erl_src/riak_kv_test_backend.erl +37 -19
- data/lib/riak/client.rb +322 -272
- data/lib/riak/client/beefcake_protobuffs_backend.rb +6 -10
- data/lib/riak/client/decaying.rb +28 -0
- data/lib/riak/client/excon_backend.rb +27 -11
- data/lib/riak/client/http_backend.rb +71 -2
- data/lib/riak/client/http_backend/configuration.rb +17 -3
- data/lib/riak/client/http_backend/transport_methods.rb +3 -3
- data/lib/riak/client/net_http_backend.rb +18 -14
- data/lib/riak/client/node.rb +111 -0
- data/lib/riak/client/pool.rb +180 -0
- data/lib/riak/client/protobuffs_backend.rb +15 -5
- data/lib/riak/client/search.rb +9 -3
- data/lib/riak/link.rb +5 -7
- data/lib/riak/locale/en.yml +1 -0
- data/lib/riak/node/configuration.rb +1 -0
- data/lib/riak/node/defaults.rb +19 -6
- data/lib/riak/node/generation.rb +9 -2
- data/lib/riak/node/log.rb +2 -2
- data/lib/riak/node/version.rb +22 -16
- data/lib/riak/robject.rb +19 -3
- data/lib/riak/serializers.rb +1 -1
- data/lib/riak/test_server.rb +10 -2
- data/lib/riak/version.rb +1 -1
- data/riak-client.gemspec +3 -3
- data/spec/failover/failover.rb +59 -0
- data/spec/integration/riak/http_backends_spec.rb +2 -2
- data/spec/integration/riak/node_spec.rb +16 -24
- data/spec/integration/riak/protobuffs_backends_spec.rb +1 -1
- data/spec/integration/riak/test_server_spec.rb +4 -3
- data/spec/integration/riak/threading_spec.rb +193 -0
- data/spec/riak/beefcake_protobuffs_backend/object_methods_spec.rb +23 -0
- data/spec/riak/beefcake_protobuffs_backend_spec.rb +4 -2
- data/spec/riak/bucket_spec.rb +2 -1
- data/spec/riak/client_spec.rb +80 -181
- data/spec/riak/excon_backend_spec.rb +3 -2
- data/spec/riak/http_backend/configuration_spec.rb +37 -5
- data/spec/riak/http_backend/object_methods_spec.rb +1 -1
- data/spec/riak/http_backend/transport_methods_spec.rb +2 -2
- data/spec/riak/http_backend_spec.rb +53 -3
- data/spec/riak/map_reduce_spec.rb +1 -1
- data/spec/riak/net_http_backend_spec.rb +1 -2
- data/spec/riak/node_spec.rb +173 -0
- data/spec/riak/pool_spec.rb +306 -0
- data/spec/riak/robject_spec.rb +8 -4
- data/spec/riak/search_spec.rb +66 -15
- data/spec/riak/serializers_spec.rb +12 -1
- data/spec/spec_helper.rb +9 -1
- data/spec/support/http_backend_implementation_examples.rb +6 -2
- data/spec/support/sometimes.rb +46 -0
- data/spec/support/test_server.rb +50 -19
- data/spec/support/unified_backend_examples.rb +11 -10
- data/spec/support/version_filter.rb +14 -0
- metadata +40 -29
- data/lib/active_support/cache/riak_store.rb +0 -2
- data/lib/riak/cache_store.rb +0 -84
- data/lib/riak/client/pump.rb +0 -30
- data/lib/riak/util/fiber1.8.rb +0 -48
- data/spec/integration/riak/cache_store_spec.rb +0 -129
data/lib/riak/robject.rb
CHANGED
@@ -50,14 +50,29 @@ module Riak
|
|
50
50
|
# @see http://wiki.basho.com/display/RIAK/REST+API#RESTAPI-Storeaneworexistingobjectwithakey Riak Rest API Docs
|
51
51
|
attr_accessor :prevent_stale_writes
|
52
52
|
|
53
|
+
# Defines a callback to be invoked when there is conflict.
|
54
|
+
#
|
55
|
+
# @yield The conflict callback.
|
56
|
+
# @yieldparam [RObject] robject The conflicted RObject
|
57
|
+
# @yieldreturn [RObject, nil] Either the resolved RObject or nil if your
|
58
|
+
# callback cannot resolve it. The next registered
|
59
|
+
# callback will be given the chance to resolve it.
|
60
|
+
#
|
61
|
+
# @note Ripple registers its own document-level conflict handler, so if you're
|
62
|
+
# using ripple, you will probably want to use that instead.
|
53
63
|
def self.on_conflict(&conflict_hook)
|
54
64
|
on_conflict_hooks << conflict_hook
|
55
65
|
end
|
56
66
|
|
67
|
+
# @return [Array<Proc>] the list of registered conflict callbacks.
|
57
68
|
def self.on_conflict_hooks
|
58
69
|
@on_conflict_hooks ||= []
|
59
70
|
end
|
60
71
|
|
72
|
+
# Attempts to resolve conflict using the registered conflict callbacks.
|
73
|
+
#
|
74
|
+
# @return [RObject] the RObject
|
75
|
+
# @note There is no guarantee the returned RObject will have been resolved
|
61
76
|
def attempt_conflict_resolution
|
62
77
|
return self unless conflict?
|
63
78
|
|
@@ -195,9 +210,10 @@ module Riak
|
|
195
210
|
|
196
211
|
# Returns sibling objects when in conflict.
|
197
212
|
# @return [Array<RObject>] an array of conflicting sibling objects for this key
|
198
|
-
# @return [self]
|
213
|
+
# @return [Array<self>] a single-element array containing object when not
|
214
|
+
# in conflict
|
199
215
|
def siblings
|
200
|
-
return self unless conflict?
|
216
|
+
return [self] unless conflict?
|
201
217
|
@siblings
|
202
218
|
end
|
203
219
|
|
@@ -244,7 +260,7 @@ module Riak
|
|
244
260
|
# @param [Array<Hash,WalkSpec>] link specifications for the query
|
245
261
|
def walk(*params)
|
246
262
|
specs = WalkSpec.normalize(*params)
|
247
|
-
@bucket.client.
|
263
|
+
@bucket.client.link_walk(self, specs)
|
248
264
|
end
|
249
265
|
|
250
266
|
# Converts the object to a link suitable for linking other objects
|
data/lib/riak/serializers.rb
CHANGED
@@ -22,7 +22,7 @@ module Riak
|
|
22
22
|
private
|
23
23
|
|
24
24
|
def serializer_for(content_type)
|
25
|
-
serializers.fetch(content_type) do
|
25
|
+
serializers.fetch(content_type[/^[^;\s]+/]) do
|
26
26
|
raise NotImplementedError.new(t('serializer_not_implemented', :content_type => content_type.inspect))
|
27
27
|
end
|
28
28
|
end
|
data/lib/riak/test_server.rb
CHANGED
@@ -17,7 +17,6 @@ module Riak
|
|
17
17
|
configuration[:env] ||= {}
|
18
18
|
configuration[:env][:riak_kv] ||= {}
|
19
19
|
(configuration[:env][:riak_kv][:add_paths] ||= []) << File.expand_path("../../../erl_src", __FILE__)
|
20
|
-
configuration[:env][:riak_kv][:storage_backend] = :riak_kv_test_backend
|
21
20
|
configuration[:env][:riak_search] ||= {}
|
22
21
|
configuration[:env][:riak_search][:search_backend] = :riak_search_test_backend
|
23
22
|
super configuration
|
@@ -52,7 +51,7 @@ module Riak
|
|
52
51
|
def drop
|
53
52
|
begin
|
54
53
|
maybe_attach
|
55
|
-
@console.command "
|
54
|
+
@console.command "#{kv_backend}:reset()."
|
56
55
|
@console.command "riak_search_test_backend:reset()."
|
57
56
|
rescue IOError
|
58
57
|
retry
|
@@ -71,5 +70,14 @@ module Riak
|
|
71
70
|
def open?
|
72
71
|
@console && @console.open?
|
73
72
|
end
|
73
|
+
|
74
|
+
def configure_data
|
75
|
+
super
|
76
|
+
if version < "1.0.0"
|
77
|
+
env[:riak_kv][:storage_backend] = :riak_kv_test014_backend
|
78
|
+
else
|
79
|
+
env[:riak_kv][:storage_backend] = :riak_kv_test_backend
|
80
|
+
end
|
81
|
+
end
|
74
82
|
end
|
75
83
|
end
|
data/lib/riak/version.rb
CHANGED
data/riak-client.gemspec
CHANGED
@@ -8,15 +8,15 @@ Gem::Specification.new do |gem|
|
|
8
8
|
gem.summary = %Q{riak-client is a rich client for Riak, the distributed database by Basho.}
|
9
9
|
gem.description = %Q{riak-client is a rich client for Riak, the distributed database by Basho. It supports the full HTTP and Protocol Buffers interfaces including storage operations, bucket configuration, link-walking, secondary indexes and map-reduce.}
|
10
10
|
gem.email = ["sean@basho.com"]
|
11
|
-
gem.homepage = "http://
|
11
|
+
gem.homepage = "http://github.com/basho/riak-ruby-client"
|
12
12
|
gem.authors = ["Sean Cribbs"]
|
13
13
|
|
14
14
|
# Deps
|
15
|
-
gem.add_development_dependency "rspec", "~>2.
|
15
|
+
gem.add_development_dependency "rspec", "~>2.8.0"
|
16
16
|
gem.add_development_dependency "fakeweb", ">=1.2"
|
17
17
|
gem.add_development_dependency "rack", ">=1.0"
|
18
18
|
gem.add_development_dependency "excon", "~>0.6.1"
|
19
|
-
gem.add_development_dependency 'rake'
|
19
|
+
gem.add_development_dependency 'rake'
|
20
20
|
gem.add_runtime_dependency "i18n", ">=0.4.0"
|
21
21
|
gem.add_runtime_dependency "builder", ">= 2.1.2"
|
22
22
|
gem.add_runtime_dependency "beefcake", "~>0.3.7"
|
@@ -0,0 +1,59 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../../lib')
|
2
|
+
require 'riak'
|
3
|
+
|
4
|
+
# This is not a formal spec yet. It's designed to be run agains a local dev
|
5
|
+
# cluster while you bring nodes up and down.
|
6
|
+
[
|
7
|
+
{:protocol => 'pbc', :protobuffs_backend => :Beefcake},
|
8
|
+
{:protocol => 'http', :http_backend => :NetHTTP},
|
9
|
+
{:protocol => 'http', :http_backend => :Excon}
|
10
|
+
].each do |opts|
|
11
|
+
@client = Riak::Client.new(
|
12
|
+
{
|
13
|
+
:nodes => (1..3).map { |i|
|
14
|
+
{
|
15
|
+
:http_port => 8090 + i,
|
16
|
+
:pb_port => 8080 + i
|
17
|
+
}
|
18
|
+
}
|
19
|
+
}.merge(opts)
|
20
|
+
)
|
21
|
+
|
22
|
+
errors = []
|
23
|
+
p opts
|
24
|
+
|
25
|
+
n = 10
|
26
|
+
c = 1000
|
27
|
+
|
28
|
+
(0...n).map do |t|
|
29
|
+
Thread.new do
|
30
|
+
# Generate a stream of put reqs. Put a . for each success, an X for
|
31
|
+
# each failure.
|
32
|
+
c.times do |i|
|
33
|
+
begin
|
34
|
+
o = @client['test'].new("#{t}:#{i}")
|
35
|
+
o.content_type = 'text/plain'
|
36
|
+
o.data = i.to_s
|
37
|
+
o.store
|
38
|
+
o2 = @client['test'].get("#{t}:#{i}")
|
39
|
+
o2.data == i.to_s or raise "wrong data"
|
40
|
+
print '.'
|
41
|
+
rescue => e
|
42
|
+
print 'X'
|
43
|
+
errors << e
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end.each do |thread|
|
48
|
+
thread.join
|
49
|
+
end
|
50
|
+
|
51
|
+
# Put errors
|
52
|
+
puts
|
53
|
+
errors.each do |e|
|
54
|
+
puts e.inspect
|
55
|
+
puts e.backtrace.map { |x| " #{x}" }.join("\n")
|
56
|
+
end
|
57
|
+
|
58
|
+
puts "\n\n"
|
59
|
+
end
|
@@ -11,7 +11,7 @@ describe "HTTP" do
|
|
11
11
|
if bklass.configured?
|
12
12
|
describe klass.to_s do
|
13
13
|
before do
|
14
|
-
@backend = bklass.new(@client)
|
14
|
+
@backend = bklass.new(@client, @client.node)
|
15
15
|
end
|
16
16
|
|
17
17
|
it_should_behave_like "Unified backend API"
|
@@ -34,7 +34,7 @@ describe "HTTP" do
|
|
34
34
|
end
|
35
35
|
|
36
36
|
describe 'NetHTTPBackend' do
|
37
|
-
subject { Riak::Client::NetHTTPBackend.new(@client) }
|
37
|
+
subject { Riak::Client::NetHTTPBackend.new(@client, @client.node) }
|
38
38
|
let(:file) { File.open(__FILE__) }
|
39
39
|
let(:sized) { Reader.new(["foo", "bar", "baz"]) }
|
40
40
|
let(:sizeless) { SizelessReader.new(["foo", "bar", "baz"]) }
|
@@ -4,34 +4,26 @@ require 'yaml'
|
|
4
4
|
|
5
5
|
describe Riak::Node, :test_server => false, :slow => true do
|
6
6
|
let(:test_server_config){ YAML.load_file("spec/support/test_server.yml") }
|
7
|
-
|
8
|
-
|
9
|
-
after
|
7
|
+
let(:node){ described_class.new(:root => ".ripplenode", :source => test_server_config['source']) }
|
8
|
+
subject { node }
|
9
|
+
after { node.stop if node.started? }
|
10
|
+
after(:all) { node.destroy }
|
11
|
+
|
12
|
+
context "finding the base_dir and version" do
|
13
|
+
its(:base_dir) { should be_directory }
|
14
|
+
its(:version) { should match /^\d+.\d+.\d+$/ }
|
15
|
+
|
16
|
+
context "when the base directory is missing" do
|
17
|
+
before { Pathname.any_instance.stub(:each_line).and_return([]) }
|
18
|
+
its(:base_dir) { should be_nil }
|
19
|
+
its(:version) { should be_nil }
|
20
|
+
end
|
21
|
+
end
|
10
22
|
|
11
23
|
context "creation" do
|
12
24
|
before { subject.create }
|
13
25
|
after { subject.destroy }
|
14
26
|
|
15
|
-
describe "finding the base_dir and version" do
|
16
|
-
it "should return a valid directory for base_dir" do
|
17
|
-
subject.base_dir.should be_exist
|
18
|
-
end
|
19
|
-
|
20
|
-
it "should read a version from the releases directory" do
|
21
|
-
subject.version.should match /\d+.\d+.\d+/
|
22
|
-
end
|
23
|
-
|
24
|
-
it "should return nil for base_dir if RUNNER_BASE_DIR is not found" do
|
25
|
-
Pathname.any_instance.stub(:readlines).and_return([])
|
26
|
-
subject.base_dir.should be_nil
|
27
|
-
end
|
28
|
-
|
29
|
-
it "should return nil for version if base_dir is nil" do
|
30
|
-
Pathname.any_instance.stub(:readlines).and_return([])
|
31
|
-
subject.version.should be_nil
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
27
|
describe "generating the manifest" do
|
36
28
|
it "should store the configuration manifest in the node directory" do
|
37
29
|
(subject.root + '.node.yml').should be_exist
|
@@ -175,7 +167,7 @@ describe Riak::Node, :test_server => false, :slow => true do
|
|
175
167
|
it "should read the console log" do
|
176
168
|
if subject.version >= "1.0.0"
|
177
169
|
subject.read_console_log(:debug, :info, :notice).should_not be_empty
|
178
|
-
subject.read_console_log(
|
170
|
+
subject.read_console_log('debug'..'emergency').should_not be_empty
|
179
171
|
subject.read_console_log(:info).should_not be_empty
|
180
172
|
subject.read_console_log(:foo).should be_empty
|
181
173
|
end
|
@@ -12,9 +12,10 @@ describe Riak::TestServer do
|
|
12
12
|
end
|
13
13
|
|
14
14
|
it "should use the KV test backend" do
|
15
|
-
subject.
|
16
|
-
subject.
|
17
|
-
|
15
|
+
backend = subject.version < "1.0.0" ? :riak_kv_test014_backend : :riak_kv_test_backend
|
16
|
+
subject.kv_backend.should == backend
|
17
|
+
subject.env[:riak_kv][:storage_backend].should == backend
|
18
|
+
app_config.should include("{storage_backend, #{backend}}")
|
18
19
|
end
|
19
20
|
|
20
21
|
it "should use the Search test backend" do
|
@@ -0,0 +1,193 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Multithreaded client", :test_server => true do
|
4
|
+
class Synchronizer
|
5
|
+
def initialize(n)
|
6
|
+
@mutex = Mutex.new
|
7
|
+
@n = n
|
8
|
+
@waiting = Set.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def sync
|
12
|
+
stop = false
|
13
|
+
@mutex.synchronize do
|
14
|
+
@waiting << Thread.current
|
15
|
+
|
16
|
+
if @waiting.size >= @n
|
17
|
+
# All threads are waiting.
|
18
|
+
@waiting.each do |t|
|
19
|
+
t.run
|
20
|
+
end
|
21
|
+
else
|
22
|
+
stop = true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
if stop
|
27
|
+
Thread.stop
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def threads(n, opts = {})
|
33
|
+
if opts[:synchronize]
|
34
|
+
s1 = Synchronizer.new n
|
35
|
+
s2 = Synchronizer.new n
|
36
|
+
end
|
37
|
+
|
38
|
+
threads = (0...n).map do |i|
|
39
|
+
Thread.new do
|
40
|
+
if opts[:synchronize]
|
41
|
+
s1.sync
|
42
|
+
end
|
43
|
+
|
44
|
+
yield i
|
45
|
+
|
46
|
+
if opts[:synchronize]
|
47
|
+
s2.sync
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
threads.each do |t|
|
53
|
+
t.join
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
[
|
58
|
+
{:protocol => 'pbc', :protobuffs_backend => :Beefcake},
|
59
|
+
{:protocol => 'http', :http_backend => :NetHTTP},
|
60
|
+
{:protocol => 'http', :http_backend => :Excon}
|
61
|
+
].each do |opts|
|
62
|
+
describe opts.inspect do
|
63
|
+
before do
|
64
|
+
@pb_port ||= $test_server.pb_port
|
65
|
+
@http_port ||= $test_server.http_port
|
66
|
+
@client = Riak::Client.new({
|
67
|
+
:pb_port => @pb_port,
|
68
|
+
:http_port => @http_port
|
69
|
+
}.merge(opts))
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'should get in parallel' do
|
73
|
+
data = "the gun is good"
|
74
|
+
ro = @client['test'].new('test')
|
75
|
+
ro.content_type = "application/json"
|
76
|
+
ro.data = [data]
|
77
|
+
ro.store
|
78
|
+
|
79
|
+
threads 10, :synchronize => true do
|
80
|
+
x = @client['test']['test']
|
81
|
+
x.content_type.should == "application/json"
|
82
|
+
x.data.should == [data]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'should put in parallel' do
|
87
|
+
data = "the tabernacle is indestructible and everlasting"
|
88
|
+
|
89
|
+
n = 10
|
90
|
+
threads n, :synchronize => true do |i|
|
91
|
+
x = @client['test'].new("test-#{i}")
|
92
|
+
x.content_type = "application/json"
|
93
|
+
x.data = ["#{data}-#{i}"]
|
94
|
+
x.store
|
95
|
+
end
|
96
|
+
|
97
|
+
(0...n).each do |i|
|
98
|
+
read = @client['test']["test-#{i}"]
|
99
|
+
read.content_type.should == "application/json"
|
100
|
+
read.data.should == ["#{data}-#{i}"]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# This is a 1.0+ spec because putting with the same client ID
|
105
|
+
# will not create siblings on 0.14 in the same way. This will
|
106
|
+
# also likely fail for nodes with vnode_vclocks = false.
|
107
|
+
it 'should put conflicts in parallel', :version => "1.0.0" do
|
108
|
+
@client['test'].allow_mult = true
|
109
|
+
@client['test'].allow_mult.should == true
|
110
|
+
|
111
|
+
init = @client['test'].new('test')
|
112
|
+
init.content_type = "application/json"
|
113
|
+
init.data = ''
|
114
|
+
init.store
|
115
|
+
|
116
|
+
# Create conflicting writes
|
117
|
+
n = 10
|
118
|
+
s = Synchronizer.new n
|
119
|
+
threads n, :synchronize => true do |i|
|
120
|
+
x = @client['test']["test"]
|
121
|
+
s.sync
|
122
|
+
x.data = [i]
|
123
|
+
x.store
|
124
|
+
end
|
125
|
+
|
126
|
+
read = @client['test']["test"]
|
127
|
+
read.conflict?.should == true
|
128
|
+
read.siblings.map do |sibling|
|
129
|
+
sibling.data.first
|
130
|
+
end.to_set.should == (0...n).to_set
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'should list-keys and get in parallel', :slow => true do
|
134
|
+
count = 100
|
135
|
+
threads = 2
|
136
|
+
|
137
|
+
# Create items
|
138
|
+
count.times do |i|
|
139
|
+
o = @client['test'].new("#{i}")
|
140
|
+
o.content_type = 'application/json'
|
141
|
+
o.data = [i]
|
142
|
+
o.store
|
143
|
+
end
|
144
|
+
|
145
|
+
threads(threads) do
|
146
|
+
set = Set.new
|
147
|
+
@client['test'].keys do |stream|
|
148
|
+
stream.each do |key|
|
149
|
+
set.merge @client['test'][key].data
|
150
|
+
end
|
151
|
+
end
|
152
|
+
set.should == (0...count).to_set
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
sometimes 'should mapreduce in parallel' do
|
157
|
+
if ("1.0.0"..."1.1.0").include?(test_server.version)
|
158
|
+
# On a fresh node, this module might not have been loaded yet
|
159
|
+
# and the mapred test exposes a race condition in riak_pipe_v
|
160
|
+
# when verifying function validity. This race condition is
|
161
|
+
# fixed in 1.1.
|
162
|
+
test_server.with_console do |console|
|
163
|
+
console.command 'code:load(riak_kv_pipe_get), ok.'
|
164
|
+
console.command 'code:load(riak_kv_mrc_map), ok.'
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
count = 10
|
169
|
+
threads = 10
|
170
|
+
|
171
|
+
# Create items
|
172
|
+
count.times do |i|
|
173
|
+
o = @client['test'].new("#{i}")
|
174
|
+
o.content_type = 'application/json'
|
175
|
+
o.data = i
|
176
|
+
o.store
|
177
|
+
end
|
178
|
+
|
179
|
+
# Ze mapreduce
|
180
|
+
threads(threads) do
|
181
|
+
# Mapreduce
|
182
|
+
(0...count).inject(Riak::MapReduce.new(@client)) do |mr, i|
|
183
|
+
mr.add('test', i.to_s)
|
184
|
+
end.map(%{function(v) {
|
185
|
+
return [v.key];
|
186
|
+
}}, :keep => true).run.map do |s|
|
187
|
+
s.to_i
|
188
|
+
end.to_set.should == (0...count).to_set
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|