better-riak-client 1.0.5
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/LICENSE +16 -0
- data/README.markdown +198 -0
- data/RELEASE_NOTES.md +211 -0
- data/better-riak-client.gemspec +61 -0
- data/erl_src/riak_kv_test014_backend.beam +0 -0
- data/erl_src/riak_kv_test014_backend.erl +189 -0
- data/erl_src/riak_kv_test_backend.beam +0 -0
- data/erl_src/riak_kv_test_backend.erl +697 -0
- data/erl_src/riak_search_test_backend.beam +0 -0
- data/erl_src/riak_search_test_backend.erl +175 -0
- data/lib/riak/bucket.rb +221 -0
- data/lib/riak/client/beefcake/messages.rb +213 -0
- data/lib/riak/client/beefcake/object_methods.rb +111 -0
- data/lib/riak/client/beefcake_protobuffs_backend.rb +226 -0
- data/lib/riak/client/decaying.rb +36 -0
- data/lib/riak/client/excon_backend.rb +162 -0
- data/lib/riak/client/feature_detection.rb +88 -0
- data/lib/riak/client/http_backend/configuration.rb +211 -0
- data/lib/riak/client/http_backend/key_streamer.rb +43 -0
- data/lib/riak/client/http_backend/object_methods.rb +106 -0
- data/lib/riak/client/http_backend/request_headers.rb +34 -0
- data/lib/riak/client/http_backend/transport_methods.rb +201 -0
- data/lib/riak/client/http_backend.rb +340 -0
- data/lib/riak/client/net_http_backend.rb +82 -0
- data/lib/riak/client/node.rb +115 -0
- data/lib/riak/client/protobuffs_backend.rb +173 -0
- data/lib/riak/client/search.rb +91 -0
- data/lib/riak/client.rb +540 -0
- data/lib/riak/cluster.rb +151 -0
- data/lib/riak/core_ext/blank.rb +53 -0
- data/lib/riak/core_ext/deep_dup.rb +13 -0
- data/lib/riak/core_ext/extract_options.rb +7 -0
- data/lib/riak/core_ext/json.rb +15 -0
- data/lib/riak/core_ext/slice.rb +18 -0
- data/lib/riak/core_ext/stringify_keys.rb +10 -0
- data/lib/riak/core_ext/symbolize_keys.rb +10 -0
- data/lib/riak/core_ext/to_param.rb +31 -0
- data/lib/riak/core_ext.rb +7 -0
- data/lib/riak/encoding.rb +6 -0
- data/lib/riak/failed_request.rb +81 -0
- data/lib/riak/i18n.rb +5 -0
- data/lib/riak/json.rb +52 -0
- data/lib/riak/link.rb +94 -0
- data/lib/riak/locale/en.yml +53 -0
- data/lib/riak/locale/fr.yml +52 -0
- data/lib/riak/map_reduce/filter_builder.rb +103 -0
- data/lib/riak/map_reduce/phase.rb +98 -0
- data/lib/riak/map_reduce.rb +225 -0
- data/lib/riak/map_reduce_error.rb +7 -0
- data/lib/riak/node/configuration.rb +293 -0
- data/lib/riak/node/console.rb +133 -0
- data/lib/riak/node/control.rb +207 -0
- data/lib/riak/node/defaults.rb +83 -0
- data/lib/riak/node/generation.rb +106 -0
- data/lib/riak/node/log.rb +34 -0
- data/lib/riak/node/version.rb +43 -0
- data/lib/riak/node.rb +38 -0
- data/lib/riak/robject.rb +318 -0
- data/lib/riak/search.rb +3 -0
- data/lib/riak/serializers.rb +74 -0
- data/lib/riak/stamp.rb +77 -0
- data/lib/riak/test_server.rb +89 -0
- data/lib/riak/util/escape.rb +76 -0
- data/lib/riak/util/headers.rb +53 -0
- data/lib/riak/util/multipart/stream_parser.rb +62 -0
- data/lib/riak/util/multipart.rb +52 -0
- data/lib/riak/util/tcp_socket_extensions.rb +58 -0
- data/lib/riak/util/translation.rb +19 -0
- data/lib/riak/version.rb +3 -0
- data/lib/riak/walk_spec.rb +105 -0
- data/lib/riak.rb +21 -0
- metadata +348 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
|
2
|
+
require 'riak/util/headers'
|
3
|
+
|
4
|
+
module Riak
|
5
|
+
class Client
|
6
|
+
class HTTPBackend
|
7
|
+
# @private
|
8
|
+
class RequestHeaders < Riak::Util::Headers
|
9
|
+
alias each each_capitalized
|
10
|
+
|
11
|
+
def initialize(hash)
|
12
|
+
initialize_http_header(hash)
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_a
|
16
|
+
[].tap do |arr|
|
17
|
+
each_capitalized do |k,v|
|
18
|
+
arr << "#{k}: #{v}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_hash
|
24
|
+
{}.tap do |hash|
|
25
|
+
each_capitalized do |k,v|
|
26
|
+
hash[k] ||= []
|
27
|
+
hash[k] << v
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,201 @@
|
|
1
|
+
|
2
|
+
require 'base64'
|
3
|
+
require 'uri'
|
4
|
+
require 'riak/client'
|
5
|
+
require 'riak/util/headers'
|
6
|
+
|
7
|
+
module Riak
|
8
|
+
class Client
|
9
|
+
class HTTPBackend
|
10
|
+
# Methods related to performing HTTP requests in a consistent
|
11
|
+
# fashion across multiple client libraries. HTTP/1.1 verbs are
|
12
|
+
# presented as methods.
|
13
|
+
module TransportMethods
|
14
|
+
# Performs a HEAD request to the specified resource on the Riak server.
|
15
|
+
# @param [Fixnum, Array] expect the expected HTTP response code(s) from Riak
|
16
|
+
# @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
|
17
|
+
# @overload head(expect, *resource)
|
18
|
+
# @overload head(expect, *resource, headers)
|
19
|
+
# Send the request with custom headers
|
20
|
+
# @param [Hash] headers custom headers to send with the request
|
21
|
+
# @return [Hash] response data, containing only the :headers and :code keys
|
22
|
+
# @raise [FailedRequest] if the response code doesn't match the expected response
|
23
|
+
def head(expect, resource, headers={})
|
24
|
+
headers = default_headers.merge(headers)
|
25
|
+
perform(:head, resource, headers, expect)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Performs a GET request to the specified resource on the Riak server.
|
29
|
+
# @param [Fixnum, Array] expect the expected HTTP response code(s) from Riak
|
30
|
+
# @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
|
31
|
+
# @overload get(expect, *resource)
|
32
|
+
# @overload get(expect, *resource, headers)
|
33
|
+
# Send the request with custom headers
|
34
|
+
# @param [Hash] headers custom headers to send with the request
|
35
|
+
# @overload get(expect, *resource, headers={})
|
36
|
+
# Stream the response body through the supplied block
|
37
|
+
# @param [Hash] headers custom headers to send with the request
|
38
|
+
# @yield [chunk] yields successive chunks of the response body as strings
|
39
|
+
# @return [Hash] response data, containing only the :headers and :code keys
|
40
|
+
# @return [Hash] response data, containing :headers, :body, and :code keys
|
41
|
+
# @raise [FailedRequest] if the response code doesn't match the expected response
|
42
|
+
def get(expect, resource, headers={}, &block)
|
43
|
+
headers = default_headers.merge(headers)
|
44
|
+
perform(:get, resource, headers, expect, &block)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Performs a PUT request to the specified resource on the Riak server.
|
48
|
+
# @param [Fixnum, Array] expect the expected HTTP response code(s) from Riak
|
49
|
+
# @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
|
50
|
+
# @param [String] body the request body to send to the server
|
51
|
+
# @overload put(expect, *resource, body)
|
52
|
+
# @overload put(expect, *resource, body, headers)
|
53
|
+
# Send the request with custom headers
|
54
|
+
# @param [Hash] headers custom headers to send with the request
|
55
|
+
# @overload put(expect, *resource, body, headers={})
|
56
|
+
# Stream the response body through the supplied block
|
57
|
+
# @param [Hash] headers custom headers to send with the request
|
58
|
+
# @yield [chunk] yields successive chunks of the response body as strings
|
59
|
+
# @return [Hash] response data, containing only the :headers and :code keys
|
60
|
+
# @return [Hash] response data, containing :headers, :code, and :body keys
|
61
|
+
# @raise [FailedRequest] if the response code doesn't match the expected response
|
62
|
+
def put(expect, resource, body, headers={}, &block)
|
63
|
+
headers = default_headers.merge(headers)
|
64
|
+
verify_body!(body)
|
65
|
+
perform(:put, resource, headers, expect, body, &block)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Performs a POST request to the specified resource on the Riak server.
|
69
|
+
# @param [Fixnum, Array] expect the expected HTTP response code(s) from Riak
|
70
|
+
# @param [String, Array<String>] resource a relative path or array of path segments that will be joined to the root URI
|
71
|
+
# @param [String] body the request body to send to the server
|
72
|
+
# @overload post(expect, *resource, body)
|
73
|
+
# @overload post(expect, *resource, body, headers)
|
74
|
+
# Send the request with custom headers
|
75
|
+
# @param [Hash] headers custom headers to send with the request
|
76
|
+
# @overload post(expect, *resource, body, headers={})
|
77
|
+
# Stream the response body through the supplied block
|
78
|
+
# @param [Hash] headers custom headers to send with the request
|
79
|
+
# @yield [chunk] yields successive chunks of the response body as strings
|
80
|
+
# @return [Hash] response data, containing only the :headers and :code keys
|
81
|
+
# @return [Hash] response data, containing :headers, :code and :body keys
|
82
|
+
# @raise [FailedRequest] if the response code doesn't match the expected response
|
83
|
+
def post(expect, resource, body, headers={}, &block)
|
84
|
+
headers = default_headers.merge(headers)
|
85
|
+
verify_body!(body)
|
86
|
+
perform(:post, resource, headers, expect, body, &block)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Performs a DELETE request to the specified resource on the Riak server.
|
90
|
+
# @param [Fixnum, Array] expect the expected HTTP response code(s) from Riak
|
91
|
+
# @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
|
92
|
+
# @overload delete(expect, *resource)
|
93
|
+
# @overload delete(expect, *resource, headers)
|
94
|
+
# Send the request with custom headers
|
95
|
+
# @param [Hash] headers custom headers to send with the request
|
96
|
+
# @overload delete(expect, *resource, headers={})
|
97
|
+
# Stream the response body through the supplied block
|
98
|
+
# @param [Hash] headers custom headers to send with the request
|
99
|
+
# @yield [chunk] yields successive chunks of the response body as strings
|
100
|
+
# @return [Hash] response data, containing only the :headers and :code keys
|
101
|
+
# @return [Hash] response data, containing :headers, :code and :body keys
|
102
|
+
# @raise [FailedRequest] if the response code doesn't match the expected response
|
103
|
+
def delete(expect, resource, headers={}, &block)
|
104
|
+
headers = default_headers.merge(headers)
|
105
|
+
perform(:delete, resource, headers, expect, &block)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Executes requests according to the underlying HTTP client library semantics.
|
109
|
+
# @abstract Subclasses must implement this internal method to perform HTTP requests
|
110
|
+
# according to the API of their HTTP libraries.
|
111
|
+
# @param [Symbol] method one of :head, :get, :post, :put, :delete
|
112
|
+
# @param [URI] uri the HTTP URI to request
|
113
|
+
# @param [Hash] headers headers to send along with the request
|
114
|
+
# @param [Fixnum, Array] expect the expected response code(s)
|
115
|
+
# @param [String, #read] body the PUT or POST request body
|
116
|
+
# @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.
|
117
|
+
# @yield [chunk] if the method is not :head, successive chunks of the response body will be yielded as strings
|
118
|
+
# @raise [NotImplementedError] if a subclass does not implement this method
|
119
|
+
def perform(method, uri, headers, expect, body=nil)
|
120
|
+
raise NotImplementedError
|
121
|
+
end
|
122
|
+
|
123
|
+
# Default header hash sent with every request, based on settings in the client
|
124
|
+
# @return [Hash] headers that will be merged with user-specified headers on every request
|
125
|
+
def default_headers
|
126
|
+
{
|
127
|
+
"Accept" => "multipart/mixed, application/json;q=0.7, */*;q=0.5",
|
128
|
+
"X-Riak-ClientId" => client_id
|
129
|
+
}.merge(basic_auth_header)
|
130
|
+
end
|
131
|
+
|
132
|
+
def client_id
|
133
|
+
value = @client.client_id
|
134
|
+
case value
|
135
|
+
when Integer
|
136
|
+
b64encode(value)
|
137
|
+
when String
|
138
|
+
value
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def basic_auth_header
|
143
|
+
@node.basic_auth ? {"Authorization" => "Basic #{Base64::encode64(@node.basic_auth)}"} : {}
|
144
|
+
end
|
145
|
+
|
146
|
+
# @return [URI] The calculated root URI for the Riak HTTP endpoint
|
147
|
+
def root_uri
|
148
|
+
protocol = node.ssl_enabled? ? "https" : "http"
|
149
|
+
URI.parse("#{protocol}://#{node.host}:#{node.http_port}")
|
150
|
+
end
|
151
|
+
|
152
|
+
# Calculates an absolute URI from a relative path specification
|
153
|
+
# @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
|
154
|
+
# @return [URI] an absolute URI for the resource
|
155
|
+
def path(*segments)
|
156
|
+
segments = segments.flatten
|
157
|
+
query = segments.extract_options!.to_param
|
158
|
+
root_uri.merge(segments.join("/").gsub(/\/+/, "/").sub(/^\//, '')).tap do |uri|
|
159
|
+
uri.query = query if query.present?
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Checks the expected response codes against the actual response code. Use internally when
|
164
|
+
# implementing {#perform}.
|
165
|
+
# @param [String, Fixnum, Array<String,Fixnum>] expected the expected response code(s)
|
166
|
+
# @param [String, Fixnum] actual the received response code
|
167
|
+
# @return [Boolean] whether the actual response code is acceptable given the expectations
|
168
|
+
def valid_response?(expected, actual)
|
169
|
+
Array(expected).map(&:to_i).include?(actual.to_i)
|
170
|
+
end
|
171
|
+
|
172
|
+
# Checks whether a combination of the HTTP method, response code, and block should
|
173
|
+
# result in returning the :body in the response hash. Use internally when implementing {#perform}.
|
174
|
+
# @param [Symbol] method the HTTP method
|
175
|
+
# @param [String, Fixnum] code the received response code
|
176
|
+
# @param [Boolean] has_block whether a streaming block was passed to {#perform}. Pass block_given? to this parameter.
|
177
|
+
# @return [Boolean] whether to return the body in the response hash
|
178
|
+
def return_body?(method, code, has_block)
|
179
|
+
method != :head && !valid_response?([204,205,304], code) && !has_block
|
180
|
+
end
|
181
|
+
|
182
|
+
# Checks whether the submitted body is valid. That is, it must
|
183
|
+
# be a String or respond to the 'read' method.
|
184
|
+
# @param [String, #read] body the body
|
185
|
+
# @raise [ArgumentError] if the body is of invalid type
|
186
|
+
def verify_body!(body)
|
187
|
+
raise ArgumentError, t('request_body_type') unless String === body || body.respond_to?(:read)
|
188
|
+
end
|
189
|
+
|
190
|
+
private
|
191
|
+
def response_headers
|
192
|
+
Thread.current[:response_headers] ||= Riak::Util::Headers.new
|
193
|
+
end
|
194
|
+
|
195
|
+
def b64encode(n)
|
196
|
+
Base64.encode64([n].pack("N")).chomp
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
@@ -0,0 +1,340 @@
|
|
1
|
+
require 'riak/util/escape'
|
2
|
+
require 'riak/util/translation'
|
3
|
+
require 'riak/util/multipart'
|
4
|
+
require 'riak/util/multipart/stream_parser'
|
5
|
+
require 'riak/json'
|
6
|
+
require 'riak/client'
|
7
|
+
require 'riak/bucket'
|
8
|
+
require 'riak/robject'
|
9
|
+
require 'riak/client/http_backend/transport_methods'
|
10
|
+
require 'riak/client/http_backend/object_methods'
|
11
|
+
require 'riak/client/http_backend/configuration'
|
12
|
+
require 'riak/client/http_backend/key_streamer'
|
13
|
+
require 'riak/client/feature_detection'
|
14
|
+
|
15
|
+
module Riak
|
16
|
+
class Client
|
17
|
+
# The parent class for all backends that connect to Riak via
|
18
|
+
# HTTP. This class implements all of the universal backend API
|
19
|
+
# methods on behalf of subclasses, which need only implement the
|
20
|
+
# {TransportMethods#perform} method for library-specific
|
21
|
+
# semantics.
|
22
|
+
class HTTPBackend
|
23
|
+
include Util::Escape
|
24
|
+
include Util::Translation
|
25
|
+
include FeatureDetection
|
26
|
+
|
27
|
+
include TransportMethods
|
28
|
+
include ObjectMethods
|
29
|
+
include Configuration
|
30
|
+
|
31
|
+
# The Riak::Client that uses this backend
|
32
|
+
attr_reader :client
|
33
|
+
|
34
|
+
# The Riak::Client::Node that uses this backend
|
35
|
+
attr_reader :node
|
36
|
+
|
37
|
+
# Create an HTTPBackend for the Riak::Client.
|
38
|
+
# @param [Client] The client
|
39
|
+
# @param [Node] The node we're connecting to.
|
40
|
+
def initialize(client, node)
|
41
|
+
raise ArgumentError, t("client_type", :client => client) unless Client === client
|
42
|
+
raise ArgumentError, t("node_type", :node => node) unless Node === node
|
43
|
+
@client = client
|
44
|
+
@node = node
|
45
|
+
end
|
46
|
+
|
47
|
+
# Pings the server
|
48
|
+
# @return [true,false] whether the server is available
|
49
|
+
def ping
|
50
|
+
get(200, ping_path)
|
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 [Hash] options request quorums
|
61
|
+
# @option options [Fixnum, String, Symbol] :r the read quorum for the
|
62
|
+
# request - how many nodes should concur on the read
|
63
|
+
# @option options [Fixnum, String, Symbol] :pr the "primary"
|
64
|
+
# read quorum for the request - how many primary partitions
|
65
|
+
# must be available
|
66
|
+
# @return [RObject] the fetched object
|
67
|
+
def fetch_object(bucket, key, options={})
|
68
|
+
bucket = Bucket.new(client, bucket) if String === bucket
|
69
|
+
response = get([200,300], object_path(bucket.name, key, options))
|
70
|
+
load_object(RObject.new(bucket, key), response)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Reloads the data for a given RObject, a special case of {#fetch_object}.
|
74
|
+
def reload_object(robject, options={})
|
75
|
+
response = get([200,300,304], object_path(robject.bucket.name, robject.key, options), reload_headers(robject))
|
76
|
+
if response[:code].to_i == 304
|
77
|
+
robject
|
78
|
+
else
|
79
|
+
load_object(robject, response)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Stores an object
|
84
|
+
# @param [RObject] robject the object to store
|
85
|
+
# @param [Hash] options quorum and storage options
|
86
|
+
# @option options [true,false] :returnbody (false) whether to update the object
|
87
|
+
# after write with the new value
|
88
|
+
# @option options [Fixnum, String, Symbol] :w the write quorum
|
89
|
+
# @option options [Fixnum, String, Symbol] :pw the "primary"
|
90
|
+
# write quorum - how many primary partitions must be available
|
91
|
+
# @option options [Fixnum, String, Symbol] :dw the durable write quorum
|
92
|
+
def store_object(robject, options={})
|
93
|
+
method, codes = if robject.key.present?
|
94
|
+
[:put, [200,204,300]]
|
95
|
+
else
|
96
|
+
[:post, 201]
|
97
|
+
end
|
98
|
+
response = send(method, codes, object_path(robject.bucket.name, robject.key, options), robject.raw_data, store_headers(robject))
|
99
|
+
load_object(robject, response) if options[: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 [Hash] options quorum and delete options
|
107
|
+
# @options options [Fixnum, String, Symbol] :rw the read/write quorum for
|
108
|
+
# the request
|
109
|
+
# @options options [String] :vclock the vector clock of the
|
110
|
+
# object to be deleted
|
111
|
+
def delete_object(bucket, key, options={})
|
112
|
+
bucket = bucket.name if Bucket === bucket
|
113
|
+
vclock = options.delete(:vclock)
|
114
|
+
headers = vclock ? {"X-Riak-VClock" => vclock} : {}
|
115
|
+
delete([204, 404], object_path(bucket, key, options), headers)
|
116
|
+
end
|
117
|
+
|
118
|
+
# Fetches bucket properties
|
119
|
+
# @param [Bucket, String] bucket the bucket properties to fetch
|
120
|
+
# @return [Hash] bucket properties
|
121
|
+
def get_bucket_props(bucket)
|
122
|
+
bucket = bucket.name if Bucket === bucket
|
123
|
+
response = get(200, bucket_properties_path(bucket))
|
124
|
+
JSON.parse(response[:body])['props']
|
125
|
+
end
|
126
|
+
|
127
|
+
# Sets bucket properties
|
128
|
+
# @param [Bucket, String] bucket the bucket to set properties on
|
129
|
+
# @param [Hash] properties the properties to set
|
130
|
+
def set_bucket_props(bucket, props)
|
131
|
+
bucket = bucket.name if Bucket === bucket
|
132
|
+
body = {'props' => props}.to_json
|
133
|
+
put(204, bucket_properties_path(bucket), body, {"Content-Type" => "application/json"})
|
134
|
+
end
|
135
|
+
|
136
|
+
# List keys in a bucket
|
137
|
+
# @param [Bucket, String] bucket the bucket to fetch the keys
|
138
|
+
# for
|
139
|
+
# @yield [Array<String>] a list of keys from the current
|
140
|
+
# streamed chunk
|
141
|
+
# @return [Array<String>] the list of keys, if no block was given
|
142
|
+
def list_keys(bucket, &block)
|
143
|
+
bucket = bucket.name if Bucket === bucket
|
144
|
+
if block_given?
|
145
|
+
get(200, key_list_path(bucket, :keys => 'stream'), {}, &KeyStreamer.new(block))
|
146
|
+
else
|
147
|
+
response = get(200, key_list_path(bucket))
|
148
|
+
obj = JSON.parse(response[:body])
|
149
|
+
obj && obj['keys'].map {|k| unescape(k) }
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Lists known buckets
|
154
|
+
# @return [Array<String>] the list of buckets
|
155
|
+
def list_buckets
|
156
|
+
response = get(200, bucket_list_path)
|
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
|
+
raise MapReduceError.new(t("empty_map_reduce_query")) if mr.query.empty? && !mapred_phaseless?
|
168
|
+
if block_given?
|
169
|
+
parser = Riak::Util::Multipart::StreamParser.new do |response|
|
170
|
+
result = JSON.parse(response[:body])
|
171
|
+
yield result['phase'], result['data']
|
172
|
+
end
|
173
|
+
post(200, mapred_path({:chunked => true}), mr.to_json, {"Content-Type" => "application/json", "Accept" => "application/json"}, &parser)
|
174
|
+
nil
|
175
|
+
else
|
176
|
+
response = post(200, mapred_path, mr.to_json, {"Content-Type" => "application/json", "Accept" => "application/json"})
|
177
|
+
begin
|
178
|
+
JSON.parse(response[:body])
|
179
|
+
rescue
|
180
|
+
response
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# Gets health statistics
|
186
|
+
# @return [Hash] information about the server, including stats
|
187
|
+
def stats
|
188
|
+
response = get(200, stats_path)
|
189
|
+
JSON.parse(response[:body])
|
190
|
+
end
|
191
|
+
|
192
|
+
# Performs a link-walking query
|
193
|
+
# @param [RObject] robject the object to start at
|
194
|
+
# @param [Array<WalkSpec>] walk_specs a list of walk
|
195
|
+
# specifications to process
|
196
|
+
# @return [Array<Array<RObject>>] a list of the matched objects,
|
197
|
+
# grouped by phase
|
198
|
+
def link_walk(robject, walk_specs)
|
199
|
+
response = get(200, link_walk_path(robject.bucket.name, robject.key, walk_specs))
|
200
|
+
if boundary = Util::Multipart.extract_boundary(response[:headers]['content-type'].first)
|
201
|
+
Util::Multipart.parse(response[:body], boundary).map do |group|
|
202
|
+
group.map do |obj|
|
203
|
+
if obj[:headers] && !obj[:headers]['x-riak-deleted'] && !obj[:body].blank? && obj[:headers]['location']
|
204
|
+
link = Riak::Link.new(obj[:headers]['location'].first, "")
|
205
|
+
load_object(RObject.new(client.bucket(link.bucket), link.key), obj)
|
206
|
+
end
|
207
|
+
end.compact
|
208
|
+
end
|
209
|
+
else
|
210
|
+
[]
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# Performs a secondary-index query.
|
215
|
+
# @param [String, Bucket] bucket the bucket to query
|
216
|
+
# @param [String] index the index to query
|
217
|
+
# @param [String, Integer, Range] query the equality query or
|
218
|
+
# range query to perform
|
219
|
+
# @return [Array<String>] a list of keys matching the query
|
220
|
+
def get_index(bucket, index, query)
|
221
|
+
bucket = bucket.name if Bucket === bucket
|
222
|
+
path = case query
|
223
|
+
when Range
|
224
|
+
raise ArgumentError, t('invalid_index_query', :value => query.inspect) unless String === query.begin || Integer === query.end
|
225
|
+
index_range_path(bucket, index, query.begin, query.end)
|
226
|
+
when String, Integer
|
227
|
+
index_eq_path(bucket, index, query)
|
228
|
+
else
|
229
|
+
raise ArgumentError, t('invalid_index_query', :value => query.inspect)
|
230
|
+
end
|
231
|
+
response = get(200, path)
|
232
|
+
JSON.parse(response[:body])['keys']
|
233
|
+
end
|
234
|
+
|
235
|
+
# (Riak Search) Performs a search query
|
236
|
+
# @param [String,nil] index the index to query, or nil for the
|
237
|
+
# default
|
238
|
+
# @param [String] query the Lucene query to perform
|
239
|
+
# @param [Hash] options query options
|
240
|
+
# @see Client#search
|
241
|
+
def search(index, query, options={})
|
242
|
+
response = get(200, solr_select_path(index, query, options.stringify_keys))
|
243
|
+
if response[:headers]['content-type'].include?("application/json")
|
244
|
+
normalize_search_response JSON.parse(response[:body])
|
245
|
+
else
|
246
|
+
response[:body]
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
# (Riak Search) Updates a search index (includes deletes).
|
251
|
+
# @param [String, nil] index the index to update, or nil for the
|
252
|
+
# default index.
|
253
|
+
# @param [String] updates an XML update string in Solr's required format
|
254
|
+
# @see Client#index
|
255
|
+
def update_search_index(index, updates)
|
256
|
+
post(200, solr_update_path(index), updates, {'Content-Type' => 'text/xml'})
|
257
|
+
end
|
258
|
+
|
259
|
+
# (Luwak) Fetches a file from the Luwak large-file interface.
|
260
|
+
# @param [String] filename the name of the file
|
261
|
+
# @yield [chunk] A block which will receive individual chunks of
|
262
|
+
# the file as they are streamed
|
263
|
+
# @yieldparam [String] chunk a block of the file
|
264
|
+
# @return [IO, nil] the file (also having content_type and
|
265
|
+
# original_filename accessors). The file will need to be
|
266
|
+
# reopened to be read
|
267
|
+
def get_file(filename, &block)
|
268
|
+
if block_given?
|
269
|
+
get(200, luwak_path(filename), &block)
|
270
|
+
nil
|
271
|
+
else
|
272
|
+
tmpfile = LuwakFile.new(escape(filename))
|
273
|
+
begin
|
274
|
+
response = get(200, luwak_path(filename)) do |chunk|
|
275
|
+
tmpfile.write chunk
|
276
|
+
end
|
277
|
+
tmpfile.content_type = response[:headers]['content-type'].first
|
278
|
+
tmpfile
|
279
|
+
ensure
|
280
|
+
tmpfile.close
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
# (Luwak) Detects whether a file exists in the Luwak large-file
|
286
|
+
# interface.
|
287
|
+
# @param [String] filename the name of the file
|
288
|
+
# @return [true,false] whether the file exists
|
289
|
+
def file_exists?(filename)
|
290
|
+
result = head([200,404], luwak_path(filename))
|
291
|
+
result[:code] == 200
|
292
|
+
end
|
293
|
+
|
294
|
+
# (Luwak) Deletes a file from the Luwak large-file interface.
|
295
|
+
# @param [String] filename the name of the file
|
296
|
+
def delete_file(filename)
|
297
|
+
delete([204,404], luwak_path(filename))
|
298
|
+
end
|
299
|
+
|
300
|
+
# (Luwak) Uploads a file to the Luwak large-file interface.
|
301
|
+
# @overload store_file(filename, content_type, data)
|
302
|
+
# Stores the file at the given key/filename
|
303
|
+
# @param [String] filename the key/filename for the object
|
304
|
+
# @param [String] content_type the MIME Content-Type for the data
|
305
|
+
# @param [IO, String] data the contents of the file
|
306
|
+
# @overload store_file(content_type, data)
|
307
|
+
# Stores the file with a server-determined key/filename
|
308
|
+
# @param [String] content_type the MIME Content-Type for the data
|
309
|
+
# @param [String, #read] data the contents of the file
|
310
|
+
# @return [String] the key/filename where the object was stored
|
311
|
+
def store_file(*args)
|
312
|
+
data, content_type, filename = args.reverse
|
313
|
+
if filename
|
314
|
+
put(204, luwak_path(filename), data, {"Content-Type" => content_type})
|
315
|
+
filename
|
316
|
+
else
|
317
|
+
response = post(201, luwak_path(nil), data, {"Content-Type" => content_type})
|
318
|
+
response[:headers]["location"].first.split("/").last
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
private
|
323
|
+
def normalize_search_response(json)
|
324
|
+
{}.tap do |result|
|
325
|
+
if json['response']
|
326
|
+
result['num_found'] = json['response']['numFound']
|
327
|
+
result['max_score'] = json['response']['maxScore'].to_f
|
328
|
+
result['docs'] = json['response']['docs'].map do |d|
|
329
|
+
if d['fields']
|
330
|
+
d['fields'].merge('id' => d['id'])
|
331
|
+
else
|
332
|
+
d
|
333
|
+
end
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|