riak-client 0.8.3 → 0.9.0.beta

Sign up to get free protection for your applications and to get access to all the features.
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