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 CHANGED
@@ -26,6 +26,10 @@ gemspec = Gem::Specification.new do |gem|
26
26
  lib/riak/cache_store.rb
27
27
  lib/riak/client/curb_backend.rb
28
28
  lib/riak/client/excon_backend.rb
29
+ lib/riak/client/http_backend/configuration.rb
30
+ lib/riak/client/http_backend/object_methods.rb
31
+ lib/riak/client/http_backend/request_headers.rb
32
+ lib/riak/client/http_backend/transport_methods.rb
29
33
  lib/riak/client/http_backend.rb
30
34
  lib/riak/client/net_http_backend.rb
31
35
  lib/riak/client.rb
@@ -42,6 +46,8 @@ gemspec = Gem::Specification.new do |gem|
42
46
  lib/riak/link.rb
43
47
  lib/riak/locale
44
48
  lib/riak/locale/en.yml
49
+ lib/riak/map_reduce/filter_builder.rb
50
+ lib/riak/map_reduce/phase.rb
45
51
  lib/riak/map_reduce.rb
46
52
  lib/riak/map_reduce_error.rb
47
53
  lib/riak/robject.rb
@@ -68,6 +74,7 @@ gemspec = Gem::Specification.new do |gem|
68
74
  spec/riak/escape_spec.rb
69
75
  spec/riak/excon_backend_spec.rb
70
76
  spec/riak/headers_spec.rb
77
+ spec/riak/http_backend/object_methods_spec.rb
71
78
  spec/riak/http_backend_spec.rb
72
79
  spec/riak/link_spec.rb
73
80
  spec/riak/map_reduce_spec.rb
@@ -110,18 +117,18 @@ require 'rspec/core'
110
117
  require 'rspec/core/rake_task'
111
118
 
112
119
  desc "Run Unit Specs Only"
113
- Rspec::Core::RakeTask.new(:spec) do |spec|
120
+ RSpec::Core::RakeTask.new(:spec) do |spec|
114
121
  spec.pattern = "spec/riak/**/*_spec.rb"
115
122
  end
116
123
 
117
124
  namespace :spec do
118
125
  desc "Run Integration Specs Only"
119
- Rspec::Core::RakeTask.new(:integration) do |spec|
126
+ RSpec::Core::RakeTask.new(:integration) do |spec|
120
127
  spec.pattern = "spec/integration/**/*_spec.rb"
121
128
  end
122
129
 
123
130
  desc "Run All Specs"
124
- Rspec::Core::RakeTask.new(:all) do |spec|
131
+ RSpec::Core::RakeTask.new(:all) do |spec|
125
132
  spec.pattern = "spec/**/*_spec.rb"
126
133
  end
127
134
  end
@@ -32,23 +32,7 @@ module Riak
32
32
  def initialize(client, name)
33
33
  raise ArgumentError, t("client_type", :client => client.inspect) unless Client === client
34
34
  raise ArgumentError, t("string_type", :string => name.inspect) unless String === name
35
- @client, @name, @props = client, name, {}
36
- end
37
-
38
- # Load information for the bucket from a response given by the {Riak::Client::HTTPBackend}.
39
- # Used mostly internally - use {Riak::Client#bucket} to get a {Bucket} instance.
40
- # @param [Hash] response a response from {Riak::Client::HTTPBackend}
41
- # @return [Bucket] self
42
- # @see Client#bucket
43
- def load(response={})
44
- content_type = response[:headers]['content-type'].first rescue ""
45
- unless content_type =~ /json$/
46
- raise Riak::InvalidResponse.new({"content-type" => ["application/json"]}, response[:headers], t("loading_bucket", :name => name))
47
- end
48
- payload = JSON.parse(response[:body])
49
- @keys = payload['keys'].map {|k| CGI.unescape(k) } if payload['keys']
50
- @props = payload['props'] if payload['props']
51
- self
35
+ @client, @name = client, name
52
36
  end
53
37
 
54
38
  # Accesses or retrieves a list of keys in this bucket.
@@ -59,21 +43,15 @@ module Riak
59
43
  # @yield [Array<String>] a list of keys from the current chunk
60
44
  # @option options [Boolean] :reload (false) If present, will force reloading of the bucket's keys from Riak
61
45
  # @return [Array<String>] Keys in this bucket
62
- def keys(options={})
46
+ def keys(options={}, &block)
63
47
  if block_given?
64
- @client.http.get(200, @client.prefix, escape(name), {:props => false, :keys => 'stream'}, {}) do |chunk|
65
- obj = JSON.parse(chunk) rescue nil
66
- next unless obj and obj['keys']
67
- yield obj['keys'].map {|k| CGI.unescape(k) } if obj['keys']
68
- end
48
+ @client.backend.list_keys(self, &block)
69
49
  elsif @keys.nil? || options[:reload]
70
- response = @client.http.get(200, @client.prefix, escape(name), {:props => false, :keys => true}, {})
71
- load(response)
50
+ @keys = @client.backend.list_keys(self)
72
51
  end
73
52
  @keys
74
53
  end
75
54
 
76
-
77
55
  # Sets internal properties on the bucket
78
56
  # Note: this results in a request to the Riak server!
79
57
  # @param [Hash] properties new properties for the bucket
@@ -95,18 +73,18 @@ module Riak
95
73
  # @see #n_value, #allow_mult, #r, #w, #dw, #rw
96
74
  def props=(properties)
97
75
  raise ArgumentError, t("hash_type", :hash => properties.inspect) unless Hash === properties
98
- body = {'props' => properties}.to_json
99
- @client.http.put(204, @client.prefix, escape(name), body, {"Content-Type" => "application/json"})
100
- @props.merge!(properties)
76
+ props.merge!(properties)
77
+ @client.backend.set_bucket_props(self, properties)
78
+ props
101
79
  end
102
- alias properties= props=
80
+ alias :'properties=' :'props='
103
81
 
104
82
  # @return [Hash] Internal Riak bucket properties.
105
83
  # @see #props=
106
84
  def props
107
- @props
85
+ @props ||= @client.backend.get_bucket_props(self)
108
86
  end
109
- alias properties props
87
+ alias :properties :props
110
88
 
111
89
  # Retrieve an object from within the bucket.
112
90
  # @param [String] key the key of the object to retrieve
@@ -115,9 +93,7 @@ module Riak
115
93
  # @return [Riak::RObject] the object
116
94
  # @raise [FailedRequest] if the object is not found or some other error occurs
117
95
  def get(key, options={})
118
- code = allow_mult ? [200,300] : 200
119
- response = @client.http.get(code, @client.prefix, escape(name), escape(key), options, {})
120
- RObject.new(self, key).load(response)
96
+ @client.backend.fetch_object(self, key, options[:r])
121
97
  end
122
98
  alias :[] :get
123
99
 
@@ -151,8 +127,12 @@ module Riak
151
127
  # @option options [Fixnum] :r - the read quorum value for the request (R)
152
128
  # @return [true, false] whether the key exists in this bucket
153
129
  def exists?(key, options={})
154
- result = client.http.head([200,404], client.prefix, escape(name), escape(key), options, {})
155
- result[:code] == 200
130
+ begin
131
+ get(key, options)
132
+ true
133
+ rescue Riak::FailedRequest
134
+ false
135
+ end
156
136
  end
157
137
  alias :exist? :exists?
158
138
 
@@ -161,7 +141,7 @@ module Riak
161
141
  # @param [Hash] options quorum options
162
142
  # @option options [Fixnum] :rw - the read/write quorum for the delete
163
143
  def delete(key, options={})
164
- client.http.delete([204,404], client.prefix, escape(name), escape(key), options, {})
144
+ client.backend.delete_object(self, key, options[:rw])
165
145
  end
166
146
 
167
147
  # @return [true, false] whether the bucket allows divergent siblings
@@ -181,7 +161,7 @@ module Riak
181
161
  props['n_val']
182
162
  end
183
163
  alias :n_val :n_value
184
-
164
+
185
165
  # Set the N value (number of replicas). *NOTE* This will result in a PUT request to Riak.
186
166
  # Setting this value after the bucket has objects stored in it may have unpredictable results.
187
167
  # @param [Fixnum] value the number of replicas the bucket should keep of each object
@@ -189,8 +169,8 @@ module Riak
189
169
  self.props = {'n_val' => value}
190
170
  value
191
171
  end
192
- alias :n_val= :n_value=
193
-
172
+ alias :'n_val=' :'n_value='
173
+
194
174
  [:r,:w,:dw,:rw].each do |q|
195
175
  class_eval <<-CODE
196
176
  def #{q}
@@ -202,11 +182,16 @@ module Riak
202
182
  value
203
183
  end
204
184
  CODE
205
- end
185
+ end
206
186
 
207
- # @return [String] a representation suitable for IRB and debugging output
208
- def inspect
209
- "#<Riak::Bucket #{client.http.path(client.prefix, escape(name)).to_s}#{" keys=[#{keys.join(',')}]" if defined?(@keys)}>"
210
- end
187
+ # @return [String] a representation suitable for IRB and debugging output
188
+ def inspect
189
+ "#<Riak::Bucket {#{name}}#{" keys=[#{keys.join(',')}]" if defined?(@keys)}>"
190
+ end
191
+
192
+ # @return [true,false] whether the other is equivalent
193
+ def ==(other)
194
+ Bucket === other && other.client == client && other.name == name
211
195
  end
212
196
  end
197
+ end
@@ -130,21 +130,35 @@ module Riak
130
130
  end
131
131
  end
132
132
 
133
+ alias :backend :http
134
+
133
135
  # Retrieves a bucket from Riak.
134
136
  # @param [String] bucket the bucket to retrieve
135
137
  # @param [Hash] options options for retrieving the bucket
136
- # @option options [Boolean] :keys (true) whether to retrieve the bucket keys
137
- # @option options [Boolean] :props (true) whether to retreive the bucket properties
138
+ # @option options [Boolean] :keys (false whether to retrieve the bucket keys
139
+ # @option options [Boolean] :props (false) whether to retreive the bucket properties
138
140
  # @return [Bucket] the requested bucket
139
141
  def bucket(name, options={})
140
142
  unless (options.keys - [:keys, :props]).empty?
141
143
  raise ArgumentError, "invalid options"
142
144
  end
143
- response = http.get(200, prefix, escape(name), {:keys => false}.merge(options), {})
144
- Bucket.new(self, name).load(response)
145
+ @bucket_cache ||= {}
146
+ (@bucket_cache[name] ||= Bucket.new(self, name)).tap do |b|
147
+ b.props if options[:props]
148
+ b.keys if options[:keys]
149
+ end
145
150
  end
146
151
  alias :[] :bucket
147
152
 
153
+ # Lists buckets which have keys stored in them.
154
+ # @note This is an expensive operation and should be used only
155
+ # in development.
156
+ # @return [Array<Bucket>] a list of buckets
157
+ def buckets
158
+ backend.list_buckets.map {|name| Bucket.new(self, name) }
159
+ end
160
+ alias :list_buckets :buckets
161
+
148
162
  # Stores a large file/IO object in Riak via the "Luwak" interface.
149
163
  # @overload store_file(filename, content_type, data)
150
164
  # Stores the file at the given key/filename
@@ -21,6 +21,11 @@ module Riak
21
21
  class ExconBackend < HTTPBackend
22
22
  def self.configured?
23
23
  begin
24
+ begin
25
+ require 'fiber'
26
+ rescue LoadError
27
+ require 'riak/util/fiber1.8'
28
+ end
24
29
  require 'excon'
25
30
  Excon::VERSION >= "0.3.4"
26
31
  rescue LoadError
@@ -38,7 +43,16 @@ module Riak
38
43
  params[:query] = uri.query if uri.query
39
44
  params[:body] = data if [:put,:post].include?(method)
40
45
  params[:idempotent] = (method != :post)
41
-
46
+ if block_given?
47
+ passed_block = block
48
+ Fiber.new {
49
+ f = Fiber.current
50
+ block = lambda {|chunk| f.resume(chunk) }
51
+ loop do
52
+ passed_block.call Fiber.yield
53
+ end
54
+ }.resume
55
+ end
42
56
  response = connection.request(params, &block)
43
57
  if valid_response?(expect, response.status)
44
58
  response_headers.initialize_http_header(response.headers)
@@ -13,11 +13,27 @@
13
13
  # limitations under the License.
14
14
  require 'riak'
15
15
 
16
+
16
17
  module Riak
17
18
  class Client
18
- # The parent class for all backends that connect to Riak via HTTP.
19
+ # The parent class for all backends that connect to Riak via
20
+ # HTTP. This class implements all of the universal backend API
21
+ # methods on behalf of subclasses, which need only implement the
22
+ # {TransportMethods#perform} method for library-specific
23
+ # semantics.
19
24
  class HTTPBackend
25
+ autoload :RequestHeaders, "riak/client/http_backend/request_headers"
26
+ autoload :TransportMethods, "riak/client/http_backend/transport_methods"
27
+ autoload :ObjectMethods, "riak/client/http_backend/object_methods"
28
+ autoload :Configuration, "riak/client/http_backend/configuration"
29
+
30
+ include TransportMethods
31
+ include ObjectMethods
32
+ include Configuration
33
+
34
+ include Util::Escape
20
35
  include Util::Translation
36
+
21
37
  # The Riak::Client that uses this backend
22
38
  attr_reader :client
23
39
 
@@ -28,212 +44,169 @@ module Riak
28
44
  @client = client
29
45
  end
30
46
 
31
- # Default header hash sent with every request, based on settings in the client
32
- # @return [Hash] headers that will be merged with user-specified headers on every request
33
- def default_headers
34
- {
35
- "Accept" => "multipart/mixed, application/json;q=0.7, */*;q=0.5",
36
- "X-Riak-ClientId" => @client.client_id
37
- }
38
- end
39
-
40
- # Performs a HEAD request to the specified resource on the Riak server.
41
- # @param [Fixnum, Array] expect the expected HTTP response code(s) from Riak
42
- # @param [String, Array<String,Hash>] resource a relative path or array of path segments and optional query params Hash that will be joined to the root URI
43
- # @overload head(expect, *resource)
44
- # @overload head(expect, *resource, headers)
45
- # Send the request with custom headers
46
- # @param [Hash] headers custom headers to send with the request
47
- # @return [Hash] response data, containing only the :headers and :code keys
48
- # @raise [FailedRequest] if the response code doesn't match the expected response
49
- def head(expect, *resource)
50
- headers = default_headers.merge(resource.extract_options!)
51
- verify_path!(resource)
52
- perform(:head, path(*resource), headers, expect)
53
- end
54
-
55
- # Performs a GET request to the specified resource on the Riak server.
56
- # @param [Fixnum, Array] expect the expected HTTP response code(s) from Riak
57
- # @param [String, Array<String,Hash>] resource a relative path or array of path segments and optional query params Hash that will be joined to the root URI
58
- # @overload get(expect, *resource)
59
- # @overload get(expect, *resource, headers)
60
- # Send the request with custom headers
61
- # @param [Hash] headers custom headers to send with the request
62
- # @overload get(expect, *resource, headers={})
63
- # Stream the response body through the supplied block
64
- # @param [Hash] headers custom headers to send with the request
65
- # @yield [chunk] yields successive chunks of the response body as strings
66
- # @return [Hash] response data, containing only the :headers and :code keys
67
- # @return [Hash] response data, containing :headers, :body, and :code keys
68
- # @raise [FailedRequest] if the response code doesn't match the expected response
69
- def get(expect, *resource, &block)
70
- headers = default_headers.merge(resource.extract_options!)
71
- verify_path!(resource)
72
- perform(:get, path(*resource), headers, expect, &block)
73
- end
74
-
75
- # Performs a PUT request to the specified resource on the Riak server.
76
- # @param [Fixnum, Array] expect the expected HTTP response code(s) from Riak
77
- # @param [String, Array<String,Hash>] resource a relative path or array of path segments and optional query params Hash that will be joined to the root URI
78
- # @param [String] body the request body to send to the server
79
- # @overload put(expect, *resource, body)
80
- # @overload put(expect, *resource, body, headers)
81
- # Send the request with custom headers
82
- # @param [Hash] headers custom headers to send with the request
83
- # @overload put(expect, *resource, body, headers={})
84
- # Stream the response body through the supplied block
85
- # @param [Hash] headers custom headers to send with the request
86
- # @yield [chunk] yields successive chunks of the response body as strings
87
- # @return [Hash] response data, containing only the :headers and :code keys
88
- # @return [Hash] response data, containing :headers, :code, and :body keys
89
- # @raise [FailedRequest] if the response code doesn't match the expected response
90
- def put(expect, *resource, &block)
91
- headers = default_headers.merge(resource.extract_options!)
92
- uri, data = verify_path_and_body!(resource)
93
- perform(:put, path(*uri), headers, expect, data, &block)
94
- end
95
-
96
- # Performs a POST request to the specified resource on the Riak server.
97
- # @param [Fixnum, Array] expect the expected HTTP response code(s) from Riak
98
- # @param [String, Array<String>] resource a relative path or array of path segments that will be joined to the root URI
99
- # @param [String] body the request body to send to the server
100
- # @overload post(expect, *resource, body)
101
- # @overload post(expect, *resource, body, headers)
102
- # Send the request with custom headers
103
- # @param [Hash] headers custom headers to send with the request
104
- # @overload post(expect, *resource, body, headers={})
105
- # Stream the response body through the supplied block
106
- # @param [Hash] headers custom headers to send with the request
107
- # @yield [chunk] yields successive chunks of the response body as strings
108
- # @return [Hash] response data, containing only the :headers and :code keys
109
- # @return [Hash] response data, containing :headers, :code and :body keys
110
- # @raise [FailedRequest] if the response code doesn't match the expected response
111
- def post(expect, *resource, &block)
112
- headers = default_headers.merge(resource.extract_options!)
113
- uri, data = verify_path_and_body!(resource)
114
- perform(:post, path(*uri), headers, expect, data, &block)
115
- end
116
-
117
- # Performs a DELETE request to the specified resource on the Riak server.
118
- # @param [Fixnum, Array] expect the expected HTTP response code(s) from Riak
119
- # @param [String, Array<String,Hash>] resource a relative path or array of path segments and optional query params Hash that will be joined to the root URI
120
- # @overload delete(expect, *resource)
121
- # @overload delete(expect, *resource, headers)
122
- # Send the request with custom headers
123
- # @param [Hash] headers custom headers to send with the request
124
- # @overload delete(expect, *resource, headers={})
125
- # Stream the response body through the supplied block
126
- # @param [Hash] headers custom headers to send with the request
127
- # @yield [chunk] yields successive chunks of the response body as strings
128
- # @return [Hash] response data, containing only the :headers and :code keys
129
- # @return [Hash] response data, containing :headers, :code and :body keys
130
- # @raise [FailedRequest] if the response code doesn't match the expected response
131
- def delete(expect, *resource, &block)
132
- headers = default_headers.merge(resource.extract_options!)
133
- verify_path!(resource)
134
- perform(:delete, path(*resource), headers, expect, &block)
135
- end
136
-
137
- # @return [URI] The calculated root URI for the Riak HTTP endpoint
138
- def root_uri
139
- URI.parse("http://#{@client.host}:#{@client.port}")
140
- end
141
-
142
- # Calculates an absolute URI from a relative path specification
143
- # @param [Array<String,Hash>] segments a relative path or sequence of path segments and optional query params Hash that will be joined to the root URI
144
- # @return [URI] an absolute URI for the resource
145
- def path(*segments)
146
- query = segments.extract_options!.to_param
147
- root_uri.merge(segments.join("/").gsub(/\/+/, "/").sub(/^\//, '')).tap do |uri|
148
- uri.query = query if query.present?
47
+ # Pings the server
48
+ # @return [true,false] whether the server is available
49
+ def ping
50
+ get(200, riak_kv_wm_ping, {}, {})
51
+ true
52
+ rescue
53
+ false
54
+ end
55
+
56
+ # Fetches an object by bucket/key
57
+ # @param [Bucket, String] bucket the bucket where the object is
58
+ # stored
59
+ # @param [String] key the key of the object
60
+ # @param [Fixnum, String, Symbol] r the read quorum for the
61
+ # request - how many nodes should concur on the read
62
+ # @return [RObject] the fetched object
63
+ def fetch_object(bucket, key, r=nil)
64
+ bucket = Bucket.new(client, bucket) if String === bucket
65
+ options = r ? {:r => r} : {}
66
+ response = get([200,300],riak_kv_wm_raw, escape(bucket.name), escape(key), options, {})
67
+ load_object(RObject.new(bucket, key), response)
68
+ end
69
+
70
+ # Reloads the data for a given RObject, a special case of {#fetch}.
71
+ def reload_object(robject, r = nil)
72
+ options = r ? {:r => r} : {}
73
+ response = get([200,300,304], riak_kv_wm_raw, escape(robject.bucket.name), escape(robject.key), options, reload_headers(robject))
74
+ if response[:code].to_i == 304
75
+ robject
76
+ else
77
+ load_object(robject, response)
149
78
  end
150
79
  end
151
80
 
152
- # Verifies that both a resource path and body are present in the arguments
153
- # @param [Array] args the arguments to verify
154
- # @raise [ArgumentError] if the body or resource is missing, or if the body is not a String
155
- def verify_path_and_body!(args)
156
- body = args.pop
157
- begin
158
- verify_path!(args)
159
- rescue ArgumentError
160
- raise ArgumentError, t("path_and_body_required")
81
+ # Stores an object
82
+ # @param [RObject] robject the object to store
83
+ # @param [true,false] returnbody (false) whether to update the object
84
+ # after write with the new value
85
+ # @param [Fixnum, String, Symbol] w the write quorum
86
+ # @param [Fixnum, String, Symbol] dw the durable write quorum
87
+ def store_object(robject, returnbody=false, w=nil, dw=nil)
88
+ query = {}.tap do |q|
89
+ q[:returnbody] = returnbody unless returnbody.nil?
90
+ q[:w] = w unless w.nil?
91
+ q[:dw] = dw unless dw.nil?
161
92
  end
162
-
163
- raise ArgumentError, t("request_body_type") unless String === body || IO === body
164
- [args, body]
165
- end
166
-
167
- # Verifies that the specified resource is valid
168
- # @param [String, Array] resource the resource specification
169
- # @raise [ArgumentError] if the resource path is too short
170
- def verify_path!(resource)
171
- resource = Array(resource).flatten
172
- raise ArgumentError, t("resource_path_short") unless resource.length > 1 || resource.include?(@client.mapred)
173
- end
174
-
175
- # Checks the expected response codes against the actual response code. Use internally when
176
- # implementing {#perform}.
177
- # @param [String, Fixnum, Array<String,Fixnum>] expected the expected response code(s)
178
- # @param [String, Fixnum] actual the received response code
179
- # @return [Boolean] whether the actual response code is acceptable given the expectations
180
- def valid_response?(expected, actual)
181
- Array(expected).map(&:to_i).include?(actual.to_i)
182
- end
183
-
184
- # Checks whether a combination of the HTTP method, response code, and block should
185
- # result in returning the :body in the response hash. Use internally when implementing {#perform}.
186
- # @param [Symbol] method the HTTP method
187
- # @param [String, Fixnum] code the received response code
188
- # @param [Boolean] has_block whether a streaming block was passed to {#perform}. Pass block_given? to this parameter.
189
- # @return [Boolean] whether to return the body in the response hash
190
- def return_body?(method, code, has_block)
191
- method != :head && !valid_response?([204,205,304], code) && !has_block
192
- end
193
-
194
- # Executes requests according to the underlying HTTP client library semantics.
195
- # @abstract Subclasses must implement this internal method to perform HTTP requests
196
- # according to the API of their HTTP libraries.
197
- # @param [Symbol] method one of :head, :get, :post, :put, :delete
198
- # @param [URI] uri the HTTP URI to request
199
- # @param [Hash] headers headers to send along with the request
200
- # @param [Fixnum, Array] expect the expected response code(s)
201
- # @param [String, IO] body the PUT or POST request body
202
- # @return [Hash] response data, containing :headers, :code and :body keys. Only :headers and :code should be present when the body is streamed or the method is :head.
203
- # @yield [chunk] if the method is not :head, successive chunks of the response body will be yielded as strings
204
- # @raise [NotImplementedError] if a subclass does not implement this method
205
- def perform(method, uri, headers, expect, body=nil)
206
- raise NotImplementedError
207
- end
208
-
209
- private
210
- def response_headers
211
- Thread.current[:response_headers] ||= Riak::Util::Headers.new
212
- end
213
-
214
- # @private
215
- class RequestHeaders < Riak::Util::Headers
216
- alias each each_capitalized
217
-
218
- def initialize(hash)
219
- initialize_http_header(hash)
93
+ method, codes, path = if robject.key.present?
94
+ [:put, [200,204,300], "#{escape(robject.bucket.name)}/#{escape(robject.key)}"]
95
+ else
96
+ [:post, 201, escape(robject.bucket.name)]
97
+ end
98
+ response = send(method, codes, riak_kv_wm_raw, path, query, robject.raw_data, store_headers(robject))
99
+ load_object(robject, response) if returnbody
100
+ end
101
+
102
+ # Deletes an object
103
+ # @param [Bucket, String] bucket the bucket where the object
104
+ # lives
105
+ # @param [String] key the key where the object lives
106
+ # @param [Fixnum, String, Symbol] rw the read/write quorum for
107
+ # the request
108
+ def delete_object(bucket, key, rw=nil)
109
+ bucket = bucket.name if Bucket === bucket
110
+ options = rw ? {:rw => rw} : {}
111
+ delete([204, 404], riak_kv_wm_raw, escape(bucket), escape(key), options, {})
112
+ end
113
+
114
+ # Fetches bucket properties
115
+ # @param [Bucket, String] bucket the bucket properties to fetch
116
+ # @return [Hash] bucket properties
117
+ def get_bucket_props(bucket)
118
+ bucket = bucket.name if Bucket === bucket
119
+ response = get(200, riak_kv_wm_raw, escape(bucket), {:keys => false, :props => true}, {})
120
+ JSON.parse(response[:body])['props']
121
+ end
122
+
123
+ # Sets bucket properties
124
+ # @param [Bucket, String] bucket the bucket to set properties on
125
+ # @param [Hash] properties the properties to set
126
+ def set_bucket_props(bucket, props)
127
+ bucket = bucket.name if Bucket === bucket
128
+ body = {'props' => props}.to_json
129
+ put(204, riak_kv_wm_raw, escape(bucket), body, {"Content-Type" => "application/json"})
130
+ end
131
+
132
+ # List keys in a bucket
133
+ # @param [Bucket, String] bucket the bucket to fetch the keys
134
+ # for
135
+ # @yield [Array<String>] a list of keys from the current
136
+ # streamed chunk
137
+ # @return [Array<String>] the list of keys, if no block was given
138
+ def list_keys(bucket, &block)
139
+ bucket = bucket.name if Bucket === bucket
140
+ if block_given?
141
+ get(200, riak_kv_wm_raw, escape(bucket), {:props => false, :keys => 'stream'}, {}) do |chunk|
142
+ obj = JSON.parse(chunk) rescue nil
143
+ next unless obj && obj['keys']
144
+ yield obj['keys'].map {|k| unescape(k) }
145
+ end
146
+ else
147
+ response = get(200, riak_kv_wm_raw, escape(bucket), {:props => false, :keys => true}, {})
148
+ obj = JSON.parse(response[:body])
149
+ obj && obj['keys'].map {|k| unescape(k) }
220
150
  end
151
+ end
221
152
 
222
- def to_a
223
- [].tap do |arr|
224
- each_capitalized do |k,v|
225
- arr << "#{k}: #{v}"
226
- end
153
+ # Lists known buckets
154
+ # @return [Array<String>] the list of buckets
155
+ def list_buckets
156
+ response = get(200, riak_kv_wm_raw, {:buckets => true}, {})
157
+ JSON.parse(response[:body])['buckets']
158
+ end
159
+
160
+ # Performs a MapReduce query.
161
+ # @param [MapReduce] mr the query to perform
162
+ # @yield [Fixnum, Object] the phase number and single result
163
+ # from the phase
164
+ # @return [Array<Object>] the list of results, if no block was
165
+ # given
166
+ def mapred(mr)
167
+ response = post(200, riak_kv_wm_mapred, mr.to_json, {"Content-Type" => "application/json", "Accept" => "application/json"})
168
+ data = begin
169
+ JSON.parse(response[:body])
170
+ rescue
171
+ response
172
+ end
173
+ # This fakes streaming until the streaming MIME parser works.
174
+ if block_given?
175
+ data = [data] if mr.query.count {|p| p.keep } == 1
176
+ data.each_with_index do |phase, idx|
177
+ phase.each {|obj| yield idx, obj }
227
178
  end
179
+ else
180
+ data
228
181
  end
182
+ end
229
183
 
230
- def to_hash
231
- {}.tap do |hash|
232
- each_capitalized do |k,v|
233
- hash[k] ||= []
234
- hash[k] << v
184
+ # Gets health statistics
185
+ # @return [Hash] information about the server, including stats
186
+ def stats
187
+ response = get(200, riak_kv_wm_stats, {}, {})
188
+ JSON.parse(response[:body])
189
+ end
190
+
191
+ # Performs a link-walking query
192
+ # @param [RObject] robject the object to start at
193
+ # @param [Array<WalkSpec>] walk_specs a list of walk
194
+ # specifications to process
195
+ # @return [Array<Array<RObject>>] a list of the matched objects,
196
+ # grouped by phase
197
+ def link_walk(robject, walk_specs)
198
+ response = get(200, riak_kv_wm_link_walker, escape(robject.bucket.name), escape(robject.key), walk_specs.join("/"))
199
+ if boundary = Util::Multipart.extract_boundary(response[:headers]['content-type'].first)
200
+ Util::Multipart.parse(response[:body], boundary).map do |group|
201
+ group.map do |obj|
202
+ if obj[:headers] && obj[:body] && obj[:headers]['location']
203
+ bucket, key = $1, $2 if obj[:headers]['location'].first =~ %r{/.*/(.*)/(.*)$}
204
+ load_object(RObject.new(client.bucket(bucket), key), obj)
205
+ end
235
206
  end
236
207
  end
208
+ else
209
+ []
237
210
  end
238
211
  end
239
212
  end