riak-client 1.2.0 → 1.4.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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/Gemfile +1 -7
  4. data/README.markdown +66 -0
  5. data/RELEASE_NOTES.md +27 -0
  6. data/lib/riak/bucket.rb +24 -5
  7. data/lib/riak/client.rb +42 -7
  8. data/lib/riak/client/beefcake/message_codes.rb +56 -0
  9. data/lib/riak/client/beefcake/messages.rb +190 -18
  10. data/lib/riak/client/beefcake_protobuffs_backend.rb +143 -10
  11. data/lib/riak/client/feature_detection.rb +26 -1
  12. data/lib/riak/client/http_backend.rb +58 -9
  13. data/lib/riak/client/http_backend/bucket_streamer.rb +15 -0
  14. data/lib/riak/client/http_backend/chunked_json_streamer.rb +42 -0
  15. data/lib/riak/client/http_backend/configuration.rb +17 -1
  16. data/lib/riak/client/http_backend/key_streamer.rb +4 -32
  17. data/lib/riak/client/protobuffs_backend.rb +12 -34
  18. data/lib/riak/counter.rb +101 -0
  19. data/lib/riak/index_collection.rb +71 -0
  20. data/lib/riak/list_buckets.rb +28 -0
  21. data/lib/riak/locale/en.yml +14 -0
  22. data/lib/riak/multiget.rb +123 -0
  23. data/lib/riak/node.rb +2 -0
  24. data/lib/riak/node/configuration.rb +32 -21
  25. data/lib/riak/node/defaults.rb +2 -0
  26. data/lib/riak/node/generation.rb +19 -7
  27. data/lib/riak/node/version.rb +2 -16
  28. data/lib/riak/robject.rb +1 -0
  29. data/lib/riak/secondary_index.rb +67 -0
  30. data/lib/riak/version.rb +1 -1
  31. data/riak-client.gemspec +3 -2
  32. data/spec/integration/riak/counters_spec.rb +51 -0
  33. data/spec/integration/riak/http_backends_spec.rb +24 -14
  34. data/spec/integration/riak/node_spec.rb +6 -28
  35. data/spec/riak/beefcake_protobuffs_backend_spec.rb +84 -0
  36. data/spec/riak/bucket_spec.rb +55 -5
  37. data/spec/riak/client_spec.rb +34 -0
  38. data/spec/riak/counter_spec.rb +122 -0
  39. data/spec/riak/index_collection_spec.rb +50 -0
  40. data/spec/riak/list_buckets_spec.rb +41 -0
  41. data/spec/riak/multiget_spec.rb +76 -0
  42. data/spec/riak/robject_spec.rb +4 -1
  43. data/spec/riak/secondary_index_spec.rb +225 -0
  44. data/spec/spec_helper.rb +1 -0
  45. data/spec/support/sometimes.rb +2 -2
  46. data/spec/support/unified_backend_examples.rb +4 -0
  47. metadata +41 -47
@@ -37,6 +37,90 @@ describe Riak::Client::BeefcakeProtobuffsBackend do
37
37
  backend.list_keys(exp_bucket).should == exp_keys
38
38
  end
39
39
 
40
+ context "secondary index" do
41
+ before :each do
42
+ @socket = mock(:socket).as_null_object
43
+ TCPSocket.stub(:new => @socket)
44
+ end
45
+ context 'when streaming' do
46
+ it "should stream when a block is given" do
47
+ backend.should_receive(:write_protobuff) do |msg, req|
48
+ msg.should == :IndexReq
49
+ req[:stream].should == true
50
+ end
51
+ backend.should_receive(:decode_index_response)
52
+
53
+ blk = proc{:asdf}
54
+
55
+ backend.get_index('bucket', 'words', 'asdf'..'hjkl', &blk)
56
+ end
57
+
58
+ it "should send batches of results to the block" do
59
+ backend.should_receive(:write_protobuff)
60
+
61
+ response_sets = [%w{asdf asdg asdh}, %w{gggg gggh gggi}]
62
+ response_messages = response_sets.map do |s|
63
+ Riak::Client::BeefcakeProtobuffsBackend::RpbIndexResp.new keys: s
64
+ end
65
+ response_messages.last.done = true
66
+
67
+ response_chunks = response_messages.map do |m|
68
+ encoded = m.encode
69
+ header = [encoded.length + 1, 26].pack 'NC'
70
+ [header, encoded]
71
+ end.flatten
72
+
73
+ @socket.should_receive(:read).and_return(*response_chunks)
74
+
75
+ block_body = mock 'block'
76
+ block_body.should_receive(:check).with(response_sets.first).once
77
+ block_body.should_receive(:check).with(response_sets.last).once
78
+
79
+ blk = proc {|m| block_body.check m }
80
+
81
+ backend.get_index 'bucket', 'words', 'asdf'..'hjkl', &blk
82
+ end
83
+ end
84
+
85
+ it "should return a full batch of results when not streaming" do
86
+ backend.should_receive(:write_protobuff) do |msg, req|
87
+ msg.should == :IndexReq
88
+ req[:stream].should_not be
89
+ end
90
+
91
+ response_message = Riak::Client::BeefcakeProtobuffsBackend::
92
+ RpbIndexResp.new(
93
+ keys: %w{asdf asdg asdh}
94
+ ).encode
95
+ header = [response_message.length + 1, 26].pack 'NC'
96
+ @socket.should_receive(:read).and_return(header, response_message)
97
+
98
+ results = backend.get_index 'bucket', 'words', 'asdf'..'hjkl'
99
+ results.should == %w{asdf asdg asdh}
100
+ end
101
+
102
+ it "should not crash out when no keys or terms are released" do
103
+ backend.should_receive(:write_protobuff) do |msg, req|
104
+ msg.should == :IndexReq
105
+ req[:stream].should_not be
106
+ end
107
+
108
+ response_message = Riak::Client::BeefcakeProtobuffsBackend::
109
+ RpbIndexResp.new().encode
110
+
111
+ header = [response_message.length + 1, 26].pack 'NC'
112
+ @socket.should_receive(:read).and_return(header, response_message)
113
+
114
+ results = nil
115
+ fetch = proc do
116
+ results = backend.get_index 'bucket', 'words', 'asdf'
117
+ end
118
+
119
+ fetch.should_not raise_error
120
+ results.should == []
121
+ end
122
+ end
123
+
40
124
  context "#mapred" do
41
125
  let(:mapred) { Riak::MapReduce.new(client).add('test').map("function(){}").map("function(){}") }
42
126
 
@@ -16,6 +16,7 @@ describe Riak::Bucket do
16
16
  lambda { Riak::Bucket.new("foo") }.should raise_error
17
17
  lambda { Riak::Bucket.new("foo", @client) }.should raise_error
18
18
  lambda { Riak::Bucket.new(@client, "foo") }.should_not raise_error
19
+ expect { Riak::Bucket.new(@client, '') }.to raise_error(ArgumentError)
19
20
  end
20
21
 
21
22
  it "should set the client and name attributes" do
@@ -27,12 +28,12 @@ describe Riak::Bucket do
27
28
 
28
29
  describe "accessing keys" do
29
30
  it "should list the keys" do
30
- @backend.should_receive(:list_keys).with(@bucket).and_return(["bar"])
31
+ @backend.should_receive(:list_keys).with(@bucket, {}).and_return(["bar"])
31
32
  @bucket.keys.should == ["bar"]
32
33
  end
33
34
 
34
35
  it "should allow streaming keys through block" do
35
- @backend.should_receive(:list_keys).with(@bucket).and_yield([]).and_yield(["bar"]).and_yield(["baz"])
36
+ @backend.should_receive(:list_keys).with(@bucket, {}).and_yield([]).and_yield(["bar"]).and_yield(["baz"])
36
37
  all_keys = []
37
38
  @bucket.keys do |list|
38
39
  all_keys.concat(list)
@@ -41,7 +42,7 @@ describe Riak::Bucket do
41
42
  end
42
43
 
43
44
  it "should not cache the list of keys" do
44
- @backend.should_receive(:list_keys).with(@bucket).twice.and_return(["bar"])
45
+ @backend.should_receive(:list_keys).with(@bucket, {}).twice.and_return(["bar"])
45
46
  2.times { @bucket.keys.should == ['bar'] }
46
47
  end
47
48
 
@@ -52,6 +53,24 @@ describe Riak::Bucket do
52
53
  @bucket.keys
53
54
  Riak.disable_list_keys_warnings = true
54
55
  end
56
+
57
+ it "should allow a specified timeout when listing keys" do
58
+ @backend.should_receive(:list_keys).with(@bucket, timeout: 1234).and_return(%w{bar})
59
+
60
+ keys = @bucket.keys timeout: 1234
61
+
62
+ keys.should == %w{bar}
63
+ end
64
+ end
65
+
66
+ describe "accessing a counter" do
67
+ it "should return a counter object" do
68
+ Riak::Counter.should_receive(:new).with(@bucket, 'asdf').and_return('example counter')
69
+
70
+ new_counter = @bucket.counter 'asdf'
71
+
72
+ new_counter.should == 'example counter'
73
+ end
55
74
  end
56
75
 
57
76
  describe "setting the bucket properties" do
@@ -104,6 +123,12 @@ describe Riak::Bucket do
104
123
  @backend.should_receive(:fetch_object).with(@bucket, "db", {:r => 2}).and_return(nil)
105
124
  @bucket.get("db", :r => 2)
106
125
  end
126
+
127
+ it "should disallow fetching an object with a zero-length key" do
128
+ ## TODO: This actually tests the Client object, but there is no suite
129
+ ## of tests for its generic interface.
130
+ expect { @bucket.get('') }.to raise_error(ArgumentError)
131
+ end
107
132
  end
108
133
 
109
134
  describe "creating a new blank object" do
@@ -141,10 +166,35 @@ describe Riak::Bucket do
141
166
  end
142
167
  end
143
168
 
169
+ describe "fetching multiple objects" do
170
+ it 'should get each object individually' do
171
+ @object1 = mock('obj1')
172
+ @object2 = mock('obj2')
173
+ @bucket.should_receive(:[]).with('key1').and_return(@object1)
174
+ @bucket.should_receive(:[]).with('key2').and_return(@object2)
175
+
176
+ @results = @bucket.get_many %w{key1 key2}
177
+
178
+ @results['key1'].should == @object1
179
+ @results['key2'].should == @object2
180
+ end
181
+ end
182
+
144
183
  describe "querying an index" do
145
184
  it "should list the matching keys" do
146
- @backend.should_receive(:get_index).with(@bucket, "test_bin", "testing").and_return(["bar"])
147
- @bucket.get_index("test_bin", "testing").should == ["bar"]
185
+ @backend.
186
+ should_receive(:get_index).
187
+ with(@bucket, "test_bin", "testing", {return_terms: true}).
188
+ and_return(Riak::IndexCollection.new_from_json({
189
+ 'results' => [
190
+ {'testing' => 'asdf'},
191
+ {'testing' => 'hjkl'}]
192
+ }.to_json))
193
+ result = @bucket.get_index("test_bin", "testing", return_terms: true)
194
+
195
+ result.should be_a Riak::IndexCollection
196
+ result.to_a.should == %w{asdf hjkl}
197
+ result.with_terms.should == {'testing' => %w{asdf hjkl}}
148
198
  end
149
199
  end
150
200
 
@@ -196,6 +196,29 @@ describe Riak::Client do
196
196
  end
197
197
  end
198
198
 
199
+ describe "retrieving many values" do
200
+ before :each do
201
+ @client = Riak::Client.new
202
+ @bucket = @client.bucket('foo')
203
+ @bucket.should_receive(:[]).with('value1').and_return(mock('robject'))
204
+ @bucket.should_receive(:[]).with('value2').and_return(mock('robject'))
205
+ @pairs = [
206
+ [@bucket, 'value1'],
207
+ [@bucket, 'value2']
208
+ ]
209
+ end
210
+
211
+ it 'should accept an array of bucket and key pairs' do
212
+ lambda{ @client.get_many(@pairs) }.should_not raise_error
213
+ end
214
+
215
+ it 'should return a hash of bucket/key pairs and robjects' do
216
+ @results = @client.get_many(@pairs)
217
+ @results.should be_a Hash
218
+ @results.length.should be(@pairs.length)
219
+ end
220
+ end
221
+
199
222
  describe "retrieving a bucket" do
200
223
  before :each do
201
224
  @client = Riak::Client.new
@@ -218,6 +241,10 @@ describe Riak::Client do
218
241
  @client.bucket("baz").should == @bucket
219
242
  @client.bucket("baz").should == @bucket
220
243
  end
244
+
245
+ it "should reject buckets with zero-length names" do
246
+ expect { @client.bucket('') }.to raise_error(ArgumentError)
247
+ end
221
248
  end
222
249
 
223
250
  describe "listing buckets" do
@@ -244,6 +271,13 @@ describe Riak::Client do
244
271
  @client.should_receive(:warn)
245
272
  @client.buckets
246
273
  end
274
+
275
+ it "should support a timeout option" do
276
+ @backend.should_receive(:list_buckets).with(timeout: 1234).and_return(%w{test test2})
277
+
278
+ buckets = @client.buckets timeout: 1234
279
+ buckets.should have(2).items
280
+ end
247
281
  end
248
282
 
249
283
  describe "Luwak (large-files) support" do
@@ -0,0 +1,122 @@
1
+ require 'spec_helper'
2
+
3
+ describe Riak::Counter do
4
+ describe "initialization" do
5
+ before :each do
6
+ @bucket = Riak::Bucket.allocate
7
+ @key = 'key'
8
+ @bucket.stub allow_mult: true
9
+ @bucket.stub(client: mock('client'))
10
+ @bucket.stub('is_a?' => true)
11
+ end
12
+
13
+ it "should set the bucket and key" do
14
+ ctr = Riak::Counter.new @bucket, @key
15
+ ctr.bucket.should == @bucket
16
+ ctr.key.should == @key
17
+ end
18
+
19
+ it "should require allow_mult" do
20
+ @bad_bucket = Riak::Bucket.allocate
21
+ @bad_bucket.stub allow_mult: false
22
+ @bad_bucket.stub(client: mock('client'))
23
+
24
+ expect{ctr = Riak::Counter.new @bad_bucket, @key}.to raise_error(ArgumentError)
25
+
26
+ end
27
+ end
28
+
29
+ describe "incrementing and decrementing" do
30
+ before :each do
31
+ @backend = mock 'backend'
32
+
33
+ @client = mock 'client'
34
+ @client.stub(:backend).and_yield @backend
35
+
36
+ @bucket = Riak::Bucket.allocate
37
+ @bucket.stub allow_mult: true
38
+ @bucket.stub client: @client
39
+
40
+ @key = 'key'
41
+
42
+ @ctr = Riak::Counter.new @bucket, @key
43
+
44
+ @increment_expectation = proc{|n| @backend.should_receive(:post_counter).with(@bucket, @key, n, {})}
45
+ end
46
+
47
+ it "should increment by 1 by default" do
48
+ @increment_expectation[1]
49
+ @ctr.increment
50
+ end
51
+
52
+ it "should support incrementing by positive numbers" do
53
+ @increment_expectation[15]
54
+ @ctr.increment 15
55
+ end
56
+
57
+ it "should support incrementing by negative numbers" do
58
+ @increment_expectation[-12]
59
+ @ctr.increment -12
60
+ end
61
+
62
+ it "should decrement by 1 by default" do
63
+ @increment_expectation[-1]
64
+ @ctr.decrement
65
+ end
66
+
67
+ it "should support decrementing by positive numbers" do
68
+ @increment_expectation[-30]
69
+ @ctr.decrement 30
70
+ end
71
+
72
+ it "should support decrementing by negative numbers" do
73
+ @increment_expectation[41]
74
+ @ctr.decrement -41
75
+ end
76
+
77
+ it "should forbid incrementing by non-integers" do
78
+ [1.1, nil, :'1', '1', 2.0/2, [1]].each do |candidate|
79
+ expect do
80
+ @ctr.increment candidate
81
+ raise candidate.to_s
82
+ end.to raise_error(ArgumentError)
83
+ end
84
+ end
85
+ end
86
+
87
+ describe "failure modes" do
88
+ before :each do
89
+ @nodes = 10.times.map do |n|
90
+ {pb_port: "100#{n}7"}
91
+ end
92
+
93
+ @fake_pool = mock 'pool'
94
+ @backend = mock 'backend'
95
+
96
+ @client = Riak::Client.new nodes: @nodes, protocol: 'pbc'
97
+ @client.instance_variable_set :@protobuffs_pool, @fake_pool
98
+
99
+ @fake_pool.stub(:take).and_yield(@backend)
100
+
101
+ @bucket = Riak::Bucket.allocate
102
+ @bucket.stub allow_mult: true
103
+ @bucket.stub client: @client
104
+
105
+ @key = 'key'
106
+
107
+ @expect_post = @backend.should_receive(:post_counter).with(@bucket, @key, 1, {})
108
+
109
+ @ctr = Riak::Counter.new @bucket, @key
110
+ end
111
+
112
+ it "should not retry on timeout" do
113
+ @expect_post.once.and_raise('timeout')
114
+ expect(proc { @ctr.increment }).to raise_error
115
+ end
116
+
117
+ it "should not retry on quorum failure" do
118
+ @expect_post.once.and_raise('quorum not satisfied')
119
+ expect(proc { @ctr.increment }).to raise_error
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ describe Riak::IndexCollection do
4
+ describe "json initialization" do
5
+ it "should accept a list of keys" do
6
+ @input = {
7
+ 'keys' => %w{first second third}
8
+ }.to_json
9
+ lambda { @coll = Riak::IndexCollection.new_from_json @input }.should_not raise_error
10
+ %w{first second third}.should == @coll
11
+ end
12
+ it "should accept a list of keys and a continuation" do
13
+ @input = {
14
+ 'keys' => %w{first second third},
15
+ 'continuation' => 'examplecontinuation'
16
+ }.to_json
17
+ lambda { @coll = Riak::IndexCollection.new_from_json @input }.should_not raise_error
18
+ %w{first second third}.should == @coll
19
+ @coll.continuation.should == 'examplecontinuation'
20
+ end
21
+ it "should accept a list of results hashes" do
22
+ @input = {
23
+ 'results' => [
24
+ {'first' => 'first'},
25
+ {'second' => 'second'},
26
+ {'second' => 'other'}
27
+ ]
28
+ }.to_json
29
+
30
+ lambda { @coll = Riak::IndexCollection.new_from_json @input }.should_not raise_error
31
+ %w{first second other}.should == @coll
32
+ {'first' => %w{first}, 'second' => %w{second other}}.should == @coll.with_terms
33
+ end
34
+ it "should accept a list of results hashes and a continuation" do
35
+ @input = {
36
+ 'results' => [
37
+ {'first' => 'first'},
38
+ {'second' => 'second'},
39
+ {'second' => 'other'}
40
+ ],
41
+ 'continuation' => 'examplecontinuation'
42
+ }.to_json
43
+
44
+ lambda { @coll = Riak::IndexCollection.new_from_json @input }.should_not raise_error
45
+ %w{first second other}.should == @coll
46
+ @coll.continuation.should == 'examplecontinuation'
47
+ {'first' => %w{first}, 'second' => %w{second other}}.should == @coll.with_terms
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe Riak::ListBuckets do
4
+ before :each do
5
+ @client = Riak::Client.new protocol: 'pbc'
6
+ @backend = mock 'backend'
7
+ @fake_pool = mock 'connection pool'
8
+ @fake_pool.stub(:take).and_yield(@backend)
9
+
10
+ @expect_list = @backend.should_receive(:list_buckets)
11
+
12
+ @client.instance_variable_set :@protobuffs_pool, @fake_pool
13
+ end
14
+
15
+ describe "non-streaming" do
16
+ it 'should call the backend without a block' do
17
+ @expect_list.with({}).and_return(%w{a b c d})
18
+
19
+ @client.list_buckets
20
+ end
21
+ end
22
+
23
+ describe "streaming" do
24
+ it 'should call the backend with a block' do
25
+ @expect_list.
26
+ and_yield(%w{abc abd abe}).
27
+ and_yield(%w{bbb ccc ddd})
28
+
29
+ @yielded = []
30
+
31
+ @client.list_buckets do |bucket|
32
+ @yielded << bucket
33
+ end
34
+
35
+ @yielded.each do |b|
36
+ b.should be_a Riak::Bucket
37
+ end
38
+ @yielded.map(&:name).should == %w{abc abd abe bbb ccc ddd}
39
+ end
40
+ end
41
+ end