riak-client 0.8.3 → 0.9.0.beta
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/Rakefile +10 -3
- data/lib/riak/bucket.rb +31 -46
- data/lib/riak/client.rb +18 -4
- data/lib/riak/client/excon_backend.rb +15 -1
- data/lib/riak/client/http_backend.rb +169 -196
- data/lib/riak/client/http_backend/configuration.rb +56 -0
- data/lib/riak/client/http_backend/object_methods.rb +101 -0
- data/lib/riak/client/http_backend/request_headers.rb +46 -0
- data/lib/riak/client/http_backend/transport_methods.rb +208 -0
- data/lib/riak/core_ext/blank.rb +14 -2
- data/lib/riak/link.rb +9 -4
- data/lib/riak/locale/en.yml +2 -0
- data/lib/riak/map_reduce.rb +37 -103
- data/lib/riak/map_reduce/filter_builder.rb +106 -0
- data/lib/riak/map_reduce/phase.rb +108 -0
- data/lib/riak/robject.rb +19 -96
- data/lib/riak/util/escape.rb +7 -0
- data/riak-client.gemspec +5 -5
- data/spec/riak/bucket_spec.rb +57 -111
- data/spec/riak/client_spec.rb +97 -78
- data/spec/riak/escape_spec.rb +7 -0
- data/spec/riak/http_backend/object_methods_spec.rb +218 -0
- data/spec/riak/http_backend_spec.rb +204 -65
- data/spec/riak/link_spec.rb +8 -0
- data/spec/riak/map_reduce_spec.rb +26 -151
- data/spec/riak/object_spec.rb +36 -350
- data/spec/riak/search_spec.rb +5 -16
- data/spec/spec_helper.rb +1 -1
- metadata +29 -8
data/lib/riak/link.rb
CHANGED
@@ -22,7 +22,7 @@ module Riak
|
|
22
22
|
# @return [String] the relationship tag (or "rel") of the other resource to this one
|
23
23
|
attr_accessor :tag
|
24
24
|
alias_method :rel, :tag
|
25
|
-
alias_method :rel
|
25
|
+
alias_method :'rel=', :'tag='
|
26
26
|
|
27
27
|
# @return [String] the bucket of the related resource
|
28
28
|
attr_accessor :bucket
|
@@ -30,6 +30,10 @@ module Riak
|
|
30
30
|
# @return [String] the key of the related resource
|
31
31
|
attr_accessor :key
|
32
32
|
|
33
|
+
%w{bucket key}.each do |m|
|
34
|
+
class_eval %{ def #{m}=(value); @url = nil; @#{m} = value; end }
|
35
|
+
end
|
36
|
+
|
33
37
|
# @param [String] header_string the string value of the Link: HTTP header from a Riak response
|
34
38
|
# @return [Array<Link>] an array of Riak::Link structs parsed from the header
|
35
39
|
def self.parse(header_string)
|
@@ -56,12 +60,13 @@ module Riak
|
|
56
60
|
|
57
61
|
# @return [String] the URL (relative or absolute) of the related resource
|
58
62
|
def url
|
59
|
-
"/riak/#{escape(bucket)}" + (key.blank? ? "" : "/#{escape(key)}")
|
63
|
+
@url ||= "/riak/#{escape(bucket)}" + (key.blank? ? "" : "/#{escape(key)}")
|
60
64
|
end
|
61
65
|
|
62
66
|
def url=(value)
|
63
|
-
@
|
64
|
-
@
|
67
|
+
@url = value
|
68
|
+
@bucket = unescape($1) if value =~ %r{^/[^/]+/([^/]+)/?}
|
69
|
+
@key = unescape($1) if value =~ %r{^/[^/]+/[^/]+/([^/]+)/?}
|
65
70
|
end
|
66
71
|
|
67
72
|
def inspect; to_s; end
|
data/lib/riak/locale/en.yml
CHANGED
@@ -18,6 +18,8 @@ en:
|
|
18
18
|
content_type_undefined: "content_type is not defined!"
|
19
19
|
empty_map_reduce_query: "Specify one or more query phases to your MapReduce."
|
20
20
|
failed_request: "Expected %{expected} from Riak but received %{code}. %{body}"
|
21
|
+
filter_needs_block: "Filter %{filter} expects a block."
|
22
|
+
filter_arity_mismatch: "Filter %{filter} expects %{expected} arguments but %{received} were given."
|
21
23
|
hash_type: "invalid argument %{hash} is not a Hash"
|
22
24
|
http_configuration: "The %{backend} HTTP backend cannot be used. Please check its requirements."
|
23
25
|
hostname_invalid: "host must be a valid hostname"
|
data/lib/riak/map_reduce.rb
CHANGED
@@ -19,7 +19,12 @@ module Riak
|
|
19
19
|
include Util::Translation
|
20
20
|
include Util::Escape
|
21
21
|
|
22
|
-
|
22
|
+
autoload :Phase, "riak/map_reduce/phase"
|
23
|
+
autoload :FilterBuilder, "riak/map_reduce/filter_builder"
|
24
|
+
|
25
|
+
# @return [Array<[bucket,key]>,String,Hash<:bucket,:filters>] The
|
26
|
+
# bucket/keys for input to the job, or the bucket (all
|
27
|
+
# keys), or a hash containing the bucket and key-filters.
|
23
28
|
# @see #add
|
24
29
|
attr_accessor :inputs
|
25
30
|
|
@@ -52,9 +57,17 @@ module Riak
|
|
52
57
|
# @param [String,Bucket] bucket the bucket of the object
|
53
58
|
# @param [String] key the key of the object
|
54
59
|
# @param [String] keydata extra data to pass along with the object to the job
|
60
|
+
# @overload add(bucket, filters)
|
61
|
+
# Run the job across all keys in the bucket, with the given
|
62
|
+
# key-filters. This will replace any other inputs previously
|
63
|
+
# added. (Requires Riak 0.14)
|
64
|
+
# @param [String,Bucket] bucket the bucket to filter keys from
|
65
|
+
# @param [Array<Array>] filters a list of key-filters to apply
|
66
|
+
# to the key list
|
55
67
|
# @return [MapReduce] self
|
56
68
|
def add(*params)
|
57
|
-
params = params.dup
|
69
|
+
params = params.dup
|
70
|
+
params = params.first if Array === params.first
|
58
71
|
case params.size
|
59
72
|
when 1
|
60
73
|
p = params.first
|
@@ -69,14 +82,28 @@ module Riak
|
|
69
82
|
when 2..3
|
70
83
|
bucket = params.shift
|
71
84
|
bucket = bucket.name if Bucket === bucket
|
72
|
-
|
73
|
-
|
85
|
+
if Array === params.first
|
86
|
+
@inputs = {:bucket => escape(bucket), :filters => params.first }
|
87
|
+
else
|
88
|
+
key = params.shift
|
89
|
+
@inputs << params.unshift(escape(key)).unshift(escape(bucket))
|
90
|
+
end
|
74
91
|
end
|
75
92
|
self
|
76
93
|
end
|
77
94
|
alias :<< :add
|
78
95
|
alias :include :add
|
79
96
|
|
97
|
+
# Adds a bucket and key-filters built by the given
|
98
|
+
# block. Equivalent to #add with a list of filters.
|
99
|
+
# @param [String] bucket the bucket to apply key-filters to
|
100
|
+
# @yield [] builder block - instance_eval'ed into a FilterBuilder
|
101
|
+
# @return [MapReduce] self
|
102
|
+
# @see MapReduce#add
|
103
|
+
def filter(bucket, &block)
|
104
|
+
add(bucket, FilterBuilder.new(&block).to_a)
|
105
|
+
end
|
106
|
+
|
80
107
|
# Add a map phase to the job.
|
81
108
|
# @overload map(function)
|
82
109
|
# @param [String, Array] function a Javascript function that represents the phase, or an Erlang [module,function] pair
|
@@ -144,16 +171,13 @@ module Riak
|
|
144
171
|
end
|
145
172
|
|
146
173
|
# Executes this map-reduce job.
|
147
|
-
# @return [Array<Array>] similar to link-walking, each element is
|
148
|
-
|
174
|
+
# @return [Array<Array>] similar to link-walking, each element is
|
175
|
+
# an array of results from a phase where "keep" is true. If there
|
176
|
+
# is only one "keep" phase, only the results from that phase will
|
177
|
+
# be returned.
|
178
|
+
def run(&block)
|
149
179
|
raise MapReduceError.new(t("empty_map_reduce_query")) if @query.empty?
|
150
|
-
|
151
|
-
begin
|
152
|
-
raise unless response[:headers]['content-type'].include?('application/json')
|
153
|
-
JSON.parse(response[:body])
|
154
|
-
rescue
|
155
|
-
response
|
156
|
-
end
|
180
|
+
@client.backend.mapred(self, &block)
|
157
181
|
rescue FailedRequest => fr
|
158
182
|
if fr.code == 500 && fr.headers['content-type'].include?("application/json")
|
159
183
|
raise MapReduceError.new(fr.body)
|
@@ -161,95 +185,5 @@ module Riak
|
|
161
185
|
raise fr
|
162
186
|
end
|
163
187
|
end
|
164
|
-
|
165
|
-
# Represents an individual phase in a map-reduce pipeline. Generally you'll want to call
|
166
|
-
# methods of MapReduce instead of using this directly.
|
167
|
-
class Phase
|
168
|
-
include Util::Translation
|
169
|
-
# @return [Symbol] the type of phase - :map, :reduce, or :link
|
170
|
-
attr_accessor :type
|
171
|
-
|
172
|
-
# @return [String, Array<String, String>, Hash, WalkSpec] For :map and :reduce types, the Javascript function to run (as a string or hash with bucket/key), or the module + function in Erlang to run. For a :link type, a {Riak::WalkSpec} or an equivalent hash.
|
173
|
-
attr_accessor :function
|
174
|
-
|
175
|
-
# @return [String] the language of the phase's function - "javascript" or "erlang". Meaningless for :link type phases.
|
176
|
-
attr_accessor :language
|
177
|
-
|
178
|
-
# @return [Boolean] whether results of this phase will be returned
|
179
|
-
attr_accessor :keep
|
180
|
-
|
181
|
-
# @return [Array] any extra static arguments to pass to the phase
|
182
|
-
attr_accessor :arg
|
183
|
-
|
184
|
-
# Creates a phase in the map-reduce pipeline
|
185
|
-
# @param [Hash] options options for the phase
|
186
|
-
# @option options [Symbol] :type one of :map, :reduce, :link
|
187
|
-
# @option options [String] :language ("javascript") "erlang" or "javascript"
|
188
|
-
# @option options [String, Array, Hash] :function In the case of Javascript, a literal function in a string, or a hash with :bucket and :key. In the case of Erlang, an Array of [module, function]. For a :link phase, a hash including any of :bucket, :tag or a WalkSpec.
|
189
|
-
# @option options [Boolean] :keep (false) whether to return the results of this phase
|
190
|
-
# @option options [Array] :arg (nil) any extra static arguments to pass to the phase
|
191
|
-
def initialize(options={})
|
192
|
-
self.type = options[:type]
|
193
|
-
self.language = options[:language] || "javascript"
|
194
|
-
self.function = options[:function]
|
195
|
-
self.keep = options[:keep] || false
|
196
|
-
self.arg = options[:arg]
|
197
|
-
end
|
198
|
-
|
199
|
-
def type=(value)
|
200
|
-
raise ArgumentError, t("invalid_phase_type") unless value.to_s =~ /^(map|reduce|link)$/i
|
201
|
-
@type = value.to_s.downcase.to_sym
|
202
|
-
end
|
203
|
-
|
204
|
-
def function=(value)
|
205
|
-
case value
|
206
|
-
when Array
|
207
|
-
raise ArgumentError, t("module_function_pair_required") unless value.size == 2
|
208
|
-
@language = "erlang"
|
209
|
-
when Hash
|
210
|
-
raise ArgumentError, t("stored_function_invalid") unless type == :link || value.has_key?(:bucket) && value.has_key?(:key)
|
211
|
-
@language = "javascript"
|
212
|
-
when String
|
213
|
-
@language = "javascript"
|
214
|
-
when WalkSpec
|
215
|
-
raise ArgumentError, t("walk_spec_invalid_unless_link") unless type == :link
|
216
|
-
else
|
217
|
-
raise ArgumentError, t("invalid_function_value", :value => value.inspect)
|
218
|
-
end
|
219
|
-
@function = value
|
220
|
-
end
|
221
|
-
|
222
|
-
# Converts the phase to JSON for use while invoking a job.
|
223
|
-
# @return [String] a JSON representation of the phase
|
224
|
-
def to_json(*a)
|
225
|
-
as_json.to_json(*a)
|
226
|
-
end
|
227
|
-
|
228
|
-
# Converts the phase to its JSON-compatible representation for job invocation.
|
229
|
-
# @return [Hash] a Hash-equivalent of the phase
|
230
|
-
def as_json(options=nil)
|
231
|
-
obj = case type
|
232
|
-
when :map, :reduce
|
233
|
-
defaults = {"language" => language, "keep" => keep}
|
234
|
-
case function
|
235
|
-
when Hash
|
236
|
-
defaults.merge(function)
|
237
|
-
when String
|
238
|
-
if function =~ /\s*function/
|
239
|
-
defaults.merge("source" => function)
|
240
|
-
else
|
241
|
-
defaults.merge("name" => function)
|
242
|
-
end
|
243
|
-
when Array
|
244
|
-
defaults.merge("module" => function[0], "function" => function[1])
|
245
|
-
end
|
246
|
-
when :link
|
247
|
-
spec = WalkSpec.normalize(function).first
|
248
|
-
{"bucket" => spec.bucket, "tag" => spec.tag, "keep" => spec.keep || keep}
|
249
|
-
end
|
250
|
-
obj["arg"] = arg if arg
|
251
|
-
{ type => obj }
|
252
|
-
end
|
253
|
-
end
|
254
188
|
end
|
255
189
|
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# Copyright 2010 Sean Cribbs, Sonian Inc., and Basho Technologies, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
require 'riak'
|
15
|
+
|
16
|
+
module Riak
|
17
|
+
class MapReduce
|
18
|
+
# Builds key-filter lists for MapReduce inputs in a DSL-like fashion.
|
19
|
+
class FilterBuilder
|
20
|
+
include Util::Translation
|
21
|
+
|
22
|
+
# Known filters available in riak_kv_mapred_filters, mapped to
|
23
|
+
# their arities. These are turned into instance methods.
|
24
|
+
# Example:
|
25
|
+
#
|
26
|
+
# FilterBuilder.new do
|
27
|
+
# string_to_int
|
28
|
+
# less_than 50
|
29
|
+
# end
|
30
|
+
FILTERS = {
|
31
|
+
:int_to_string => 0,
|
32
|
+
:string_to_int => 0,
|
33
|
+
:float_to_string => 0,
|
34
|
+
:string_to_float => 0,
|
35
|
+
:to_upper => 0,
|
36
|
+
:to_lower => 0,
|
37
|
+
:tokenize => 2,
|
38
|
+
:urldecode => 0,
|
39
|
+
:greater_than => 1,
|
40
|
+
:less_than => 1,
|
41
|
+
:greater_than_eq => 1,
|
42
|
+
:less_than_eq => 1,
|
43
|
+
:between => [2,3],
|
44
|
+
:matches => 1,
|
45
|
+
:neq => 1,
|
46
|
+
:eq => 1,
|
47
|
+
:set_member => 1,
|
48
|
+
:similar_to => 2,
|
49
|
+
:starts_with => 1,
|
50
|
+
:ends_with => 1
|
51
|
+
}
|
52
|
+
|
53
|
+
# Available logical operations for joining filter chains. These
|
54
|
+
# are turned into instance methods with leading underscores,
|
55
|
+
# with aliases to uppercase versions.
|
56
|
+
# Example:
|
57
|
+
#
|
58
|
+
# FilterBuilder.new do
|
59
|
+
# string_to_int
|
60
|
+
# AND do
|
61
|
+
# greater_than_eq 50
|
62
|
+
# neq 100
|
63
|
+
# end
|
64
|
+
# end
|
65
|
+
LOGICAL_OPERATIONS = %w{and or not}
|
66
|
+
|
67
|
+
FILTERS.each do |f,arity|
|
68
|
+
class_eval <<-CODE
|
69
|
+
def #{f}(*args)
|
70
|
+
raise ArgumentError.new(t("filter_arity_mismatch", :filter => :#{f}, :expected => #{arity.inspect}, :received => args.size)) unless Array(#{arity.inspect}).include?(args.size)
|
71
|
+
@filters << ([:#{f}] + args)
|
72
|
+
end
|
73
|
+
CODE
|
74
|
+
end
|
75
|
+
|
76
|
+
LOGICAL_OPERATIONS.each do |op|
|
77
|
+
class_eval <<-CODE
|
78
|
+
def _#{op}(&block)
|
79
|
+
raise ArgumentError.new(t('filter_needs_block', :filter => '#{op}')) unless block_given?
|
80
|
+
@filters << [:#{op}, self.class.new(&block).to_a]
|
81
|
+
end
|
82
|
+
alias :#{op.to_s.upcase} :_#{op}
|
83
|
+
CODE
|
84
|
+
end
|
85
|
+
|
86
|
+
# Creates a new FilterBuilder. Pass a block that will be
|
87
|
+
# instance_eval'ed to construct the sequence of filters.
|
88
|
+
def initialize(&block)
|
89
|
+
@filters = []
|
90
|
+
instance_eval(&block) if block_given?
|
91
|
+
end
|
92
|
+
|
93
|
+
# Wraps multi-step filters for use inside logical
|
94
|
+
# operations. Does not correspond to an actual filter.
|
95
|
+
def sequence(&block)
|
96
|
+
@filters << self.class.new(&block).to_a
|
97
|
+
end
|
98
|
+
alias :seq :sequence
|
99
|
+
|
100
|
+
# @return A list of filters for handing to the MapReduce inputs.
|
101
|
+
def to_a
|
102
|
+
@filters
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# Copyright 2010 Sean Cribbs, Sonian Inc., and Basho Technologies, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
require 'riak'
|
15
|
+
|
16
|
+
module Riak
|
17
|
+
class MapReduce
|
18
|
+
# Represents an individual phase in a map-reduce pipeline. Generally you'll want to call
|
19
|
+
# methods of MapReduce instead of using this directly.
|
20
|
+
class Phase
|
21
|
+
include Util::Translation
|
22
|
+
# @return [Symbol] the type of phase - :map, :reduce, or :link
|
23
|
+
attr_accessor :type
|
24
|
+
|
25
|
+
# @return [String, Array<String, String>, Hash, WalkSpec] For :map and :reduce types, the Javascript function to run (as a string or hash with bucket/key), or the module + function in Erlang to run. For a :link type, a {Riak::WalkSpec} or an equivalent hash.
|
26
|
+
attr_accessor :function
|
27
|
+
|
28
|
+
# @return [String] the language of the phase's function - "javascript" or "erlang". Meaningless for :link type phases.
|
29
|
+
attr_accessor :language
|
30
|
+
|
31
|
+
# @return [Boolean] whether results of this phase will be returned
|
32
|
+
attr_accessor :keep
|
33
|
+
|
34
|
+
# @return [Array] any extra static arguments to pass to the phase
|
35
|
+
attr_accessor :arg
|
36
|
+
|
37
|
+
# Creates a phase in the map-reduce pipeline
|
38
|
+
# @param [Hash] options options for the phase
|
39
|
+
# @option options [Symbol] :type one of :map, :reduce, :link
|
40
|
+
# @option options [String] :language ("javascript") "erlang" or "javascript"
|
41
|
+
# @option options [String, Array, Hash] :function In the case of Javascript, a literal function in a string, or a hash with :bucket and :key. In the case of Erlang, an Array of [module, function]. For a :link phase, a hash including any of :bucket, :tag or a WalkSpec.
|
42
|
+
# @option options [Boolean] :keep (false) whether to return the results of this phase
|
43
|
+
# @option options [Array] :arg (nil) any extra static arguments to pass to the phase
|
44
|
+
def initialize(options={})
|
45
|
+
self.type = options[:type]
|
46
|
+
self.language = options[:language] || "javascript"
|
47
|
+
self.function = options[:function]
|
48
|
+
self.keep = options[:keep] || false
|
49
|
+
self.arg = options[:arg]
|
50
|
+
end
|
51
|
+
|
52
|
+
def type=(value)
|
53
|
+
raise ArgumentError, t("invalid_phase_type") unless value.to_s =~ /^(map|reduce|link)$/i
|
54
|
+
@type = value.to_s.downcase.to_sym
|
55
|
+
end
|
56
|
+
|
57
|
+
def function=(value)
|
58
|
+
case value
|
59
|
+
when Array
|
60
|
+
raise ArgumentError, t("module_function_pair_required") unless value.size == 2
|
61
|
+
@language = "erlang"
|
62
|
+
when Hash
|
63
|
+
raise ArgumentError, t("stored_function_invalid") unless type == :link || value.has_key?(:bucket) && value.has_key?(:key)
|
64
|
+
@language = "javascript"
|
65
|
+
when String
|
66
|
+
@language = "javascript"
|
67
|
+
when WalkSpec
|
68
|
+
raise ArgumentError, t("walk_spec_invalid_unless_link") unless type == :link
|
69
|
+
else
|
70
|
+
raise ArgumentError, t("invalid_function_value", :value => value.inspect)
|
71
|
+
end
|
72
|
+
@function = value
|
73
|
+
end
|
74
|
+
|
75
|
+
# Converts the phase to JSON for use while invoking a job.
|
76
|
+
# @return [String] a JSON representation of the phase
|
77
|
+
def to_json(*a)
|
78
|
+
as_json.to_json(*a)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Converts the phase to its JSON-compatible representation for job invocation.
|
82
|
+
# @return [Hash] a Hash-equivalent of the phase
|
83
|
+
def as_json(options=nil)
|
84
|
+
obj = case type
|
85
|
+
when :map, :reduce
|
86
|
+
defaults = {"language" => language, "keep" => keep}
|
87
|
+
case function
|
88
|
+
when Hash
|
89
|
+
defaults.merge(function)
|
90
|
+
when String
|
91
|
+
if function =~ /\s*function/
|
92
|
+
defaults.merge("source" => function)
|
93
|
+
else
|
94
|
+
defaults.merge("name" => function)
|
95
|
+
end
|
96
|
+
when Array
|
97
|
+
defaults.merge("module" => function[0], "function" => function[1])
|
98
|
+
end
|
99
|
+
when :link
|
100
|
+
spec = WalkSpec.normalize(function).first
|
101
|
+
{"bucket" => spec.bucket, "tag" => spec.tag, "keep" => spec.keep || keep}
|
102
|
+
end
|
103
|
+
obj["arg"] = arg if arg
|
104
|
+
{ type => obj }
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
data/lib/riak/robject.rb
CHANGED
@@ -13,14 +13,15 @@
|
|
13
13
|
# limitations under the License.
|
14
14
|
require 'riak'
|
15
15
|
require 'set'
|
16
|
+
require 'time'
|
16
17
|
|
17
18
|
module Riak
|
18
19
|
# Represents the data and metadata stored in a bucket/key pair in
|
19
|
-
# the Riak database, the base unit of data manipulation.
|
20
|
+
# the Riak database, the base unit of data manipulation.
|
20
21
|
class RObject
|
21
|
-
include Util
|
22
22
|
include Util::Translation
|
23
23
|
include Util::Escape
|
24
|
+
extend Util::Escape
|
24
25
|
|
25
26
|
# @return [Bucket] the bucket in which this object is contained
|
26
27
|
attr_accessor :bucket
|
@@ -57,7 +58,7 @@ module Riak
|
|
57
58
|
# @return [Array<RObject>] An array of RObject instances
|
58
59
|
def self.load_from_mapreduce(client, response)
|
59
60
|
response.map do |item|
|
60
|
-
RObject.new(client[
|
61
|
+
RObject.new(client[unescape(item['bucket'])], unescape(item['key'])).load_from_mapreduce(item)
|
61
62
|
end
|
62
63
|
end
|
63
64
|
|
@@ -72,27 +73,6 @@ module Riak
|
|
72
73
|
yield self if block_given?
|
73
74
|
end
|
74
75
|
|
75
|
-
# Load object data from an HTTP response
|
76
|
-
# @param [Hash] response a response from {Riak::Client::HTTPBackend}
|
77
|
-
def load(response)
|
78
|
-
extract_header(response, "location", :key) {|v| URI.unescape(v.split("/").last) }
|
79
|
-
extract_header(response, "content-type", :content_type)
|
80
|
-
extract_header(response, "x-riak-vclock", :vclock)
|
81
|
-
extract_header(response, "link", :links) {|v| Set.new(Link.parse(v)) }
|
82
|
-
extract_header(response, "etag", :etag)
|
83
|
-
extract_header(response, "last-modified", :last_modified) {|v| Time.httpdate(v) }
|
84
|
-
@meta = response[:headers].inject({}) do |h,(k,v)|
|
85
|
-
if k =~ /x-riak-meta-(.*)/
|
86
|
-
h[$1] = v
|
87
|
-
end
|
88
|
-
h
|
89
|
-
end
|
90
|
-
@conflict = (response[:code].to_i == 300 && content_type =~ /multipart\/mixed/) rescue false
|
91
|
-
@siblings = nil
|
92
|
-
self.raw_data = response[:body] if response[:body].present?
|
93
|
-
self
|
94
|
-
end
|
95
|
-
|
96
76
|
# Load object data from a map/reduce response item.
|
97
77
|
# This method is used by RObject::load_from_mapreduce to instantiate the necessary
|
98
78
|
# objects.
|
@@ -115,7 +95,7 @@ module Riak
|
|
115
95
|
self
|
116
96
|
end
|
117
97
|
|
118
|
-
# @return [Object] the unmarshaled form of {#raw_data} stored in riak at this object's key
|
98
|
+
# @return [Object] the unmarshaled form of {#raw_data} stored in riak at this object's key
|
119
99
|
def data
|
120
100
|
if @raw_data && !@data
|
121
101
|
@data = deserialize(@raw_data)
|
@@ -147,37 +127,6 @@ module Riak
|
|
147
127
|
@raw_data = new_raw_data
|
148
128
|
end
|
149
129
|
|
150
|
-
# HTTP header hash that will be sent along when storing the object
|
151
|
-
# @return [Hash] hash of HTTP Headers
|
152
|
-
def store_headers
|
153
|
-
{}.tap do |hash|
|
154
|
-
hash["Content-Type"] = @content_type
|
155
|
-
hash["X-Riak-Vclock"] = @vclock if @vclock
|
156
|
-
if @prevent_stale_writes && @etag.present?
|
157
|
-
hash["If-Match"] = @etag
|
158
|
-
elsif @prevent_stale_writes
|
159
|
-
hash["If-None-Match"] = "*"
|
160
|
-
end
|
161
|
-
unless @links.blank?
|
162
|
-
hash["Link"] = @links.reject {|l| l.rel == "up" }.map(&:to_s).join(", ")
|
163
|
-
end
|
164
|
-
unless @meta.blank?
|
165
|
-
@meta.each do |k,v|
|
166
|
-
hash["X-Riak-Meta-#{k}"] = v.to_s
|
167
|
-
end
|
168
|
-
end
|
169
|
-
end
|
170
|
-
end
|
171
|
-
|
172
|
-
# HTTP header hash that will be sent along when reloading the object
|
173
|
-
# @return [Hash] hash of HTTP headers
|
174
|
-
def reload_headers
|
175
|
-
{}.tap do |h|
|
176
|
-
h['If-None-Match'] = @etag if @etag.present?
|
177
|
-
h['If-Modified-Since'] = @last_modified.httpdate if @last_modified.present?
|
178
|
-
end
|
179
|
-
end
|
180
|
-
|
181
130
|
# Store the object in Riak
|
182
131
|
# @param [Hash] options query parameters
|
183
132
|
# @option options [Fixnum] :r the "r" parameter (Read quorum for the implicit read performed when validating the store operation)
|
@@ -189,23 +138,22 @@ module Riak
|
|
189
138
|
def store(options={})
|
190
139
|
raise ArgumentError, t("content_type_undefined") unless @content_type.present?
|
191
140
|
params = {:returnbody => true}.merge(options)
|
192
|
-
|
193
|
-
|
194
|
-
load(response)
|
141
|
+
@bucket.client.backend.store_object(self, params[:returnbody], params[:w], params[:dw])
|
142
|
+
self
|
195
143
|
end
|
196
144
|
|
197
145
|
# Reload the object from Riak. Will use conditional GETs when possible.
|
198
146
|
# @param [Hash] options query parameters
|
199
147
|
# @option options [Fixnum] :r the "r" parameter (Read quorum)
|
200
|
-
# @option options [Boolean] :force will force a reload request if
|
148
|
+
# @option options [Boolean] :force will force a reload request if
|
149
|
+
# the vclock is not present, useful for reloading the object after
|
150
|
+
# a store (not passed in the query params)
|
201
151
|
# @return [Riak::RObject] self
|
202
152
|
def reload(options={})
|
203
153
|
force = options.delete(:force)
|
204
154
|
return self unless @key && (@vclock || force)
|
205
|
-
|
206
|
-
|
207
|
-
load(response) unless response[:code] == 304
|
208
|
-
self
|
155
|
+
self.etag = self.last_modified = nil if force
|
156
|
+
bucket.client.backend.reload_object(self, options[:r])
|
209
157
|
end
|
210
158
|
|
211
159
|
alias :fetch :reload
|
@@ -218,17 +166,14 @@ module Riak
|
|
218
166
|
freeze
|
219
167
|
end
|
220
168
|
|
169
|
+
attr_writer :siblings, :conflict
|
170
|
+
|
221
171
|
# Returns sibling objects when in conflict.
|
222
172
|
# @return [Array<RObject>] an array of conflicting sibling objects for this key
|
223
173
|
# @return [self] this object when not in conflict
|
224
174
|
def siblings
|
225
175
|
return self unless conflict?
|
226
|
-
@siblings
|
227
|
-
RObject.new(self.bucket, self.key) do |sibling|
|
228
|
-
sibling.load(part)
|
229
|
-
sibling.vclock = vclock
|
230
|
-
end
|
231
|
-
end
|
176
|
+
@siblings
|
232
177
|
end
|
233
178
|
|
234
179
|
# @return [true,false] Whether this object has conflicting sibling objects (divergent vclocks)
|
@@ -286,21 +231,14 @@ module Riak
|
|
286
231
|
else
|
287
232
|
@raw_data && "(#{@raw_data.size} bytes)"
|
288
233
|
end
|
289
|
-
"#<#{self.class.name} #{
|
234
|
+
"#<#{self.class.name} {#{bucket.name}#{"," + @key if @key}} [#{@content_type}]:#{body}>"
|
290
235
|
end
|
291
236
|
|
292
237
|
# Walks links from this object to other objects in Riak.
|
293
238
|
# @param [Array<Hash,WalkSpec>] link specifications for the query
|
294
239
|
def walk(*params)
|
295
240
|
specs = WalkSpec.normalize(*params)
|
296
|
-
|
297
|
-
if boundary = Multipart.extract_boundary(response[:headers]['content-type'].first)
|
298
|
-
Multipart.parse(response[:body], boundary).map do |group|
|
299
|
-
map_walk_group(group)
|
300
|
-
end
|
301
|
-
else
|
302
|
-
[]
|
303
|
-
end
|
241
|
+
@bucket.client.http.link_walk(self, specs)
|
304
242
|
end
|
305
243
|
|
306
244
|
# Converts the object to a link suitable for linking other objects
|
@@ -322,14 +260,14 @@ module Riak
|
|
322
260
|
alias :vector_clock :vclock
|
323
261
|
alias :vector_clock= :vclock=
|
324
262
|
|
325
|
-
|
263
|
+
protected
|
326
264
|
def load_map_reduce_value(hash)
|
327
265
|
metadata = hash['metadata']
|
328
266
|
extract_if_present(metadata, 'X-Riak-VTag', :etag)
|
329
267
|
extract_if_present(metadata, 'content-type', :content_type)
|
330
268
|
extract_if_present(metadata, 'X-Riak-Last-Modified', :last_modified) { |v| Time.httpdate( v ) }
|
331
269
|
extract_if_present(metadata, 'Links', :links) do |links|
|
332
|
-
Set.new( links.map { |l| Link.new(
|
270
|
+
Set.new( links.map { |l| Link.new(*l) } )
|
333
271
|
end
|
334
272
|
extract_if_present(metadata, 'X-Riak-Meta', :meta) do |meta|
|
335
273
|
Hash[
|
@@ -349,20 +287,5 @@ module Riak
|
|
349
287
|
send("#{attribute}=", value)
|
350
288
|
end
|
351
289
|
end
|
352
|
-
|
353
|
-
def extract_header(response, name, attribute=nil, &block)
|
354
|
-
extract_if_present(response[:headers], name, attribute) do |value|
|
355
|
-
block ? block.call(value[0]) : value[0]
|
356
|
-
end
|
357
|
-
end
|
358
|
-
|
359
|
-
def map_walk_group(group)
|
360
|
-
group.map do |obj|
|
361
|
-
if obj[:headers] && obj[:body] && obj[:headers]['location']
|
362
|
-
bucket, key = $1, $2 if obj[:headers]['location'].first =~ %r{/.*/(.*)/(.*)$}
|
363
|
-
RObject.new(@bucket.client.bucket(bucket, :keys => false), key).load(obj)
|
364
|
-
end
|
365
|
-
end
|
366
|
-
end
|
367
290
|
end
|
368
291
|
end
|