riakpb 0.1.2 → 0.1.3
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/lib/riak.rb +2 -2
- data/spec/riak/map_reduce_spec.rb +324 -1
- metadata +2 -2
data/lib/riak.rb
CHANGED
@@ -9,7 +9,7 @@ require 'base64'
|
|
9
9
|
require 'riak/client_pb'
|
10
10
|
|
11
11
|
module Riak
|
12
|
-
VERSION = '0.1.
|
12
|
+
VERSION = '0.1.3'
|
13
13
|
|
14
14
|
# Domain objects
|
15
15
|
autoload :I18n, 'riak/i18n'
|
@@ -31,4 +31,4 @@ module Riak
|
|
31
31
|
autoload :Encode, 'riak/util/encode'
|
32
32
|
autoload :Decode, 'riak/util/decode'
|
33
33
|
end
|
34
|
-
end
|
34
|
+
end
|
@@ -1,4 +1,327 @@
|
|
1
1
|
require File.expand_path("../spec_helper", File.dirname(__FILE__))
|
2
2
|
|
3
3
|
describe Riak::MapReduce do
|
4
|
-
|
4
|
+
before :each do
|
5
|
+
@client = Riak::Client.new
|
6
|
+
@client.rpc.stub!(:request).and_return(nil)
|
7
|
+
@mr = Riak::MapReduce.new(@client)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should require a client" do
|
11
|
+
lambda { Riak::MapReduce.new }.should raise_error
|
12
|
+
lambda { Riak::MapReduce.new(@client) }.should_not raise_error
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should initialize the inputs and query to empty arrays" do
|
16
|
+
@mr.inputs.should == []
|
17
|
+
@mr.query.should == []
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should yield itself when given a block on initializing" do
|
21
|
+
@mr2 = nil
|
22
|
+
@mr = Riak::MapReduce.new(@client) do |mr|
|
23
|
+
@mr2 = mr
|
24
|
+
end
|
25
|
+
@mr2.should == @mr
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "adding inputs" do
|
29
|
+
it "should return self for chaining" do
|
30
|
+
@mr.add("foo", "bar").should == @mr
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should add bucket/key pairs to the inputs" do
|
34
|
+
@mr.add("foo","bar")
|
35
|
+
@mr.inputs.should == [["foo","bar"]]
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should add an array containing a bucket/key pair to the inputs" do
|
39
|
+
@mr.add(["foo","bar"])
|
40
|
+
@mr.inputs.should == [["foo","bar"]]
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should add an object to the inputs by its bucket and key" do
|
44
|
+
bucket = Riak::Bucket.new(@client, "foo")
|
45
|
+
key = Riak::Key.new(bucket, "bar")
|
46
|
+
@mr.add(key)
|
47
|
+
@mr.inputs.should == [["foo", "bar"]]
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should add an array containing a bucket/key/key-data triple to the inputs" do
|
51
|
+
@mr.add(["foo","bar",1000])
|
52
|
+
@mr.inputs.should == [["foo","bar",1000]]
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should use a bucket name as the single input" do
|
56
|
+
@mr.add(Riak::Bucket.new(@client, "foo"))
|
57
|
+
@mr.inputs.should == "foo"
|
58
|
+
@mr.add("docs")
|
59
|
+
@mr.inputs.should == "docs"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
[:map, :reduce].each do |type|
|
64
|
+
describe "adding #{type} phases" do
|
65
|
+
it "should return self for chaining" do
|
66
|
+
@mr.send(type, "function(){}").should == @mr
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should accept a function string" do
|
70
|
+
@mr.send(type, "function(){}")
|
71
|
+
@mr.query.should have(1).items
|
72
|
+
phase = @mr.query.first
|
73
|
+
phase.function.should == "function(){}"
|
74
|
+
phase.type.should == type
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should accept a function and options" do
|
78
|
+
@mr.send(type, "function(){}", :keep => true)
|
79
|
+
@mr.query.should have(1).items
|
80
|
+
phase = @mr.query.first
|
81
|
+
phase.function.should == "function(){}"
|
82
|
+
phase.type.should == type
|
83
|
+
phase.keep.should be_true
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should accept a module/function pair" do
|
87
|
+
@mr.send(type, ["riak","mapsomething"])
|
88
|
+
@mr.query.should have(1).items
|
89
|
+
phase = @mr.query.first
|
90
|
+
phase.function.should == ["riak", "mapsomething"]
|
91
|
+
phase.type.should == type
|
92
|
+
phase.language.should == "erlang"
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should accept a module/function pair with extra options" do
|
96
|
+
@mr.send(type, ["riak", "mapsomething"], :arg => [1000])
|
97
|
+
@mr.query.should have(1).items
|
98
|
+
phase = @mr.query.first
|
99
|
+
phase.function.should == ["riak", "mapsomething"]
|
100
|
+
phase.type.should == type
|
101
|
+
phase.language.should == "erlang"
|
102
|
+
phase.arg.should == [1000]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "adding link phases" do
|
108
|
+
it "should return self for chaining" do
|
109
|
+
@mr.link({}).should == @mr
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should accept a Link" do
|
113
|
+
@mr.link({:tag => "next"})
|
114
|
+
@mr.query.should have(1).items
|
115
|
+
phase = @mr.query.first
|
116
|
+
phase.type.should == :link
|
117
|
+
# phase.function.should be_kind_of(Riak::WalkSpec)
|
118
|
+
phase.function[:tag].should == "next"
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should accept a Link and a statement to keep" do
|
122
|
+
@mr.link({:bucket => "foo", :keep => true})
|
123
|
+
@mr.query.should have(1).items
|
124
|
+
phase = @mr.query.first
|
125
|
+
phase.type.should == :link
|
126
|
+
# phase.function.should be_kind_of(Riak::WalkSpec)
|
127
|
+
phase.function[:bucket].should == "foo"
|
128
|
+
phase.keep.should be_true
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
describe "converting to JSON for the job" do
|
133
|
+
it "should include the inputs and query keys" do
|
134
|
+
@mr.to_json.should =~ /"inputs":/
|
135
|
+
end
|
136
|
+
|
137
|
+
it "should map phases to their JSON equivalents" do
|
138
|
+
phase = Riak::MapReduce::Phase.new(:type => :map, :function => "function(){}")
|
139
|
+
@mr.query << phase
|
140
|
+
@mr.to_json.should include('"source":"function(){}"')
|
141
|
+
@mr.to_json.should include('"query":[{"map":{')
|
142
|
+
end
|
143
|
+
|
144
|
+
it "should emit only the bucket name when the input is the whole bucket" do
|
145
|
+
@mr.add("foo")
|
146
|
+
@mr.to_json.should include('"inputs":"foo"')
|
147
|
+
end
|
148
|
+
|
149
|
+
it "should emit an array of inputs when there are multiple inputs" do
|
150
|
+
@mr.add("foo","bar",1000).add("foo","baz")
|
151
|
+
@mr.to_json.should include('"inputs":[["foo","bar",1000],["foo","baz"]]')
|
152
|
+
end
|
153
|
+
|
154
|
+
it "should add the timeout value when set" do
|
155
|
+
@mr.timeout(50000)
|
156
|
+
@mr.to_json.should include('"timeout":50000')
|
157
|
+
end
|
158
|
+
end
|
159
|
+
=begin
|
160
|
+
describe "executing the map reduce job" do
|
161
|
+
it "should issue POST request to the mapred endpoint" do
|
162
|
+
@http.should_receive(:post).with(200, "/mapred", @mr.to_json, hash_including("Content-Type" => "application/json")).and_return({:headers => {'content-type' => ["application/json"]}, :body => "{}"})
|
163
|
+
@mr.run
|
164
|
+
end
|
165
|
+
|
166
|
+
it "should vivify JSON responses" do
|
167
|
+
@http.stub!(:post).and_return(:headers => {'content-type' => ["application/json"]}, :body => '{"key":"value"}')
|
168
|
+
@mr.run.should == {"key" => "value"}
|
169
|
+
end
|
170
|
+
|
171
|
+
it "should return the full response hash for non-JSON responses" do
|
172
|
+
response = {:code => 200, :headers => {'content-type' => ["text/plain"]}, :body => 'This is some text.'}
|
173
|
+
@http.stub!(:post).and_return(response)
|
174
|
+
@mr.run.should == response
|
175
|
+
end
|
176
|
+
|
177
|
+
it "should interpret failed requests with JSON content-types as map reduce errors" do
|
178
|
+
@http.stub!(:post).and_raise(Riak::FailedRequest.new(:post, 200, 500, {"content-type" => ["application/json"]}, '{"error":"syntax error"}'))
|
179
|
+
lambda { @mr.run }.should raise_error(Riak::MapReduceError)
|
180
|
+
begin
|
181
|
+
@mr.run
|
182
|
+
rescue Riak::MapReduceError => mre
|
183
|
+
mre.message.should == '{"error":"syntax error"}'
|
184
|
+
else
|
185
|
+
fail "No exception raised!"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
it "should re-raise non-JSON error responses" do
|
190
|
+
@http.stub!(:post).and_raise(Riak::FailedRequest.new(:post, 200, 500, {"content-type" => ["text/plain"]}, 'Oops, you bwoke it.'))
|
191
|
+
lambda { @mr.run }.should raise_error(Riak::FailedRequest)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
=end
|
195
|
+
end
|
196
|
+
|
197
|
+
describe Riak::MapReduce::Phase do
|
198
|
+
before :each do
|
199
|
+
@fun = "function(v,_,_){ return v['values'][0]['data']; }"
|
200
|
+
end
|
201
|
+
|
202
|
+
it "should initialize with a type and a function" do
|
203
|
+
phase = Riak::MapReduce::Phase.new(:type => :map, :function => @fun, :language => "javascript")
|
204
|
+
phase.type.should == :map
|
205
|
+
phase.function.should == @fun
|
206
|
+
phase.language.should == "javascript"
|
207
|
+
end
|
208
|
+
|
209
|
+
it "should initialize with a type and an MF" do
|
210
|
+
phase = Riak::MapReduce::Phase.new(:type => :map, :function => ["module", "function"], :language => "erlang")
|
211
|
+
phase.type.should == :map
|
212
|
+
phase.function.should == ["module", "function"]
|
213
|
+
phase.language.should == "erlang"
|
214
|
+
end
|
215
|
+
|
216
|
+
it "should initialize with a type and a bucket/key" do
|
217
|
+
phase = Riak::MapReduce::Phase.new(:type => :map, :function => {:bucket => "funs", :key => "awesome_map"}, :language => "javascript")
|
218
|
+
phase.type.should == :map
|
219
|
+
phase.function.should == {:bucket => "funs", :key => "awesome_map"}
|
220
|
+
phase.language.should == "javascript"
|
221
|
+
end
|
222
|
+
|
223
|
+
it "should assume the language is erlang when the function is an array" do
|
224
|
+
phase = Riak::MapReduce::Phase.new(:type => :map, :function => ["module", "function"])
|
225
|
+
phase.language.should == "erlang"
|
226
|
+
end
|
227
|
+
|
228
|
+
it "should assume the language is javascript when the function is a string" do
|
229
|
+
phase = Riak::MapReduce::Phase.new(:type => :map, :function => @fun)
|
230
|
+
phase.language.should == "javascript"
|
231
|
+
end
|
232
|
+
|
233
|
+
it "should assume the language is javascript when the function is a hash" do
|
234
|
+
phase = Riak::MapReduce::Phase.new(:type => :map, :function => {:bucket => "jobs", :key => "awesome_map"})
|
235
|
+
phase.language.should == "javascript"
|
236
|
+
end
|
237
|
+
|
238
|
+
it "should accept a WalkSpec for the function when a link phase" do
|
239
|
+
# phase = Riak::MapReduce::Phase.new(:type => :link, :function => Riak::WalkSpec.new({}))
|
240
|
+
# phase.function.should be_kind_of(Riak::WalkSpec)
|
241
|
+
end
|
242
|
+
|
243
|
+
it "should raise an error if a WalkSpec is given for a phase type other than :link" do
|
244
|
+
# lambda { Riak::MapReduce::Phase.new(:type => :map, :function => Riak::WalkSpec.new({})) }.should raise_error(ArgumentError)
|
245
|
+
end
|
246
|
+
|
247
|
+
describe "converting to JSON for the job" do
|
248
|
+
before :each do
|
249
|
+
@phase = Riak::MapReduce::Phase.new(:type => :map, :function => "")
|
250
|
+
end
|
251
|
+
|
252
|
+
[:map, :reduce].each do |type|
|
253
|
+
describe "when a #{type} phase" do
|
254
|
+
before :each do
|
255
|
+
@phase.type = type
|
256
|
+
end
|
257
|
+
|
258
|
+
it "should be an object with a single key of '#{type}'" do
|
259
|
+
@phase.to_json.should =~ /^\{"#{type}":/
|
260
|
+
end
|
261
|
+
|
262
|
+
it "should include the language" do
|
263
|
+
@phase.to_json.should =~ /"language":/
|
264
|
+
end
|
265
|
+
|
266
|
+
it "should include the keep value" do
|
267
|
+
@phase.to_json.should =~ /"keep":false/
|
268
|
+
@phase.keep = true
|
269
|
+
@phase.to_json.should =~ /"keep":true/
|
270
|
+
end
|
271
|
+
|
272
|
+
it "should include the function source when the function is a source string" do
|
273
|
+
@phase.function = "function(v,_,_){ return v; }"
|
274
|
+
@phase.to_json.should include(@phase.function)
|
275
|
+
@phase.to_json.should =~ /"source":/
|
276
|
+
end
|
277
|
+
|
278
|
+
it "should include the function name when the function is not a lambda" do
|
279
|
+
@phase.function = "Riak.mapValues"
|
280
|
+
@phase.to_json.should include('"name":"Riak.mapValues"')
|
281
|
+
@phase.to_json.should_not include('"source"')
|
282
|
+
end
|
283
|
+
|
284
|
+
it "should include the bucket and key when referring to a stored function" do
|
285
|
+
@phase.function = {:bucket => "design", :key => "wordcount_map"}
|
286
|
+
@phase.to_json.should include('"bucket":"design"')
|
287
|
+
@phase.to_json.should include('"key":"wordcount_map"')
|
288
|
+
end
|
289
|
+
|
290
|
+
it "should include the module and function when invoking an Erlang function" do
|
291
|
+
@phase.function = ["riak_mapreduce", "mapreduce_fun"]
|
292
|
+
@phase.to_json.should include('"module":"riak_mapreduce"')
|
293
|
+
@phase.to_json.should include('"function":"mapreduce_fun"')
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
describe "when a link phase" do
|
299
|
+
before :each do
|
300
|
+
@phase.type = :link
|
301
|
+
@phase.function = {}
|
302
|
+
end
|
303
|
+
|
304
|
+
it "should be an object of a single key 'link'" do
|
305
|
+
@phase.to_json.should == "{\"link\":{\"keep\":false}}"
|
306
|
+
end
|
307
|
+
|
308
|
+
it "should include the bucket" do
|
309
|
+
@phase.function[:bucket] = "foo"
|
310
|
+
@phase.to_json.should == "{\"link\":{\"bucket\":\"foo\",\"keep\":false}}"
|
311
|
+
end
|
312
|
+
|
313
|
+
it "should include the tag" do
|
314
|
+
@phase.function[:tag] = "parent"
|
315
|
+
@phase.to_json.should == "{\"link\":{\"tag\":\"parent\",\"keep\":false}}"
|
316
|
+
end
|
317
|
+
|
318
|
+
it "should include the keep value" do
|
319
|
+
@phase.keep = true
|
320
|
+
@phase.to_json.should == "{\"link\":{\"keep\":true}}"
|
321
|
+
@phase.keep = false
|
322
|
+
@phase.function[:keep] = true
|
323
|
+
@phase.to_json.should == "{\"link\":{\"keep\":false}}"
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|