seomoz-riak-client 1.0.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +27 -0
- data/Guardfile +14 -0
- data/Rakefile +76 -0
- data/erl_src/riak_kv_test_backend.beam +0 -0
- data/erl_src/riak_kv_test_backend.erl +174 -0
- data/erl_src/riak_search_test_backend.beam +0 -0
- data/erl_src/riak_search_test_backend.erl +175 -0
- data/lib/active_support/cache/riak_store.rb +2 -0
- data/lib/riak.rb +21 -0
- data/lib/riak/bucket.rb +215 -0
- data/lib/riak/cache_store.rb +84 -0
- data/lib/riak/client.rb +415 -0
- data/lib/riak/client/beefcake/messages.rb +147 -0
- data/lib/riak/client/beefcake/object_methods.rb +92 -0
- data/lib/riak/client/beefcake_protobuffs_backend.rb +176 -0
- data/lib/riak/client/excon_backend.rb +65 -0
- data/lib/riak/client/http_backend.rb +203 -0
- data/lib/riak/client/http_backend/configuration.rb +46 -0
- data/lib/riak/client/http_backend/key_streamer.rb +43 -0
- data/lib/riak/client/http_backend/object_methods.rb +94 -0
- data/lib/riak/client/http_backend/request_headers.rb +34 -0
- data/lib/riak/client/http_backend/transport_methods.rb +218 -0
- data/lib/riak/client/net_http_backend.rb +79 -0
- data/lib/riak/client/protobuffs_backend.rb +97 -0
- data/lib/riak/client/pump.rb +30 -0
- data/lib/riak/client/search.rb +94 -0
- data/lib/riak/core_ext.rb +6 -0
- data/lib/riak/core_ext/blank.rb +53 -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/encoding.rb +6 -0
- data/lib/riak/failed_request.rb +81 -0
- data/lib/riak/i18n.rb +3 -0
- data/lib/riak/json.rb +28 -0
- data/lib/riak/link.rb +85 -0
- data/lib/riak/locale/en.yml +48 -0
- data/lib/riak/map_reduce.rb +206 -0
- data/lib/riak/map_reduce/filter_builder.rb +94 -0
- data/lib/riak/map_reduce/phase.rb +98 -0
- data/lib/riak/map_reduce_error.rb +7 -0
- data/lib/riak/robject.rb +290 -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 +252 -0
- data/lib/riak/util/escape.rb +45 -0
- data/lib/riak/util/fiber1.8.rb +48 -0
- data/lib/riak/util/headers.rb +53 -0
- data/lib/riak/util/multipart.rb +52 -0
- data/lib/riak/util/multipart/stream_parser.rb +62 -0
- data/lib/riak/util/tcp_socket_extensions.rb +58 -0
- data/lib/riak/util/translation.rb +19 -0
- data/lib/riak/walk_spec.rb +105 -0
- data/riak-client.gemspec +55 -0
- data/seomoz-riak-client.gemspec +55 -0
- data/spec/fixtures/cat.jpg +0 -0
- data/spec/fixtures/multipart-blank.txt +7 -0
- data/spec/fixtures/multipart-mapreduce.txt +10 -0
- data/spec/fixtures/multipart-with-body.txt +16 -0
- data/spec/fixtures/server.cert.crt +15 -0
- data/spec/fixtures/server.cert.key +15 -0
- data/spec/fixtures/test.pem +1 -0
- data/spec/integration/riak/cache_store_spec.rb +154 -0
- data/spec/integration/riak/http_backends_spec.rb +58 -0
- data/spec/integration/riak/protobuffs_backends_spec.rb +32 -0
- data/spec/integration/riak/test_server_spec.rb +161 -0
- data/spec/riak/beefcake_protobuffs_backend_spec.rb +59 -0
- data/spec/riak/bucket_spec.rb +205 -0
- data/spec/riak/client_spec.rb +517 -0
- data/spec/riak/core_ext/to_param_spec.rb +15 -0
- data/spec/riak/escape_spec.rb +69 -0
- data/spec/riak/excon_backend_spec.rb +64 -0
- data/spec/riak/headers_spec.rb +38 -0
- data/spec/riak/http_backend/configuration_spec.rb +51 -0
- data/spec/riak/http_backend/object_methods_spec.rb +217 -0
- data/spec/riak/http_backend/transport_methods_spec.rb +117 -0
- data/spec/riak/http_backend_spec.rb +269 -0
- data/spec/riak/link_spec.rb +71 -0
- data/spec/riak/map_reduce/filter_builder_spec.rb +32 -0
- data/spec/riak/map_reduce/phase_spec.rb +136 -0
- data/spec/riak/map_reduce_spec.rb +310 -0
- data/spec/riak/multipart_spec.rb +23 -0
- data/spec/riak/net_http_backend_spec.rb +16 -0
- data/spec/riak/robject_spec.rb +427 -0
- data/spec/riak/search_spec.rb +178 -0
- data/spec/riak/serializers_spec.rb +93 -0
- data/spec/riak/stamp_spec.rb +54 -0
- data/spec/riak/stream_parser_spec.rb +53 -0
- data/spec/riak/walk_spec_spec.rb +195 -0
- data/spec/spec_helper.rb +39 -0
- data/spec/support/drb_mock_server.rb +39 -0
- data/spec/support/http_backend_implementation_examples.rb +266 -0
- data/spec/support/integration_setup.rb +10 -0
- data/spec/support/mock_server.rb +81 -0
- data/spec/support/mocks.rb +4 -0
- data/spec/support/test_server.yml.example +2 -0
- data/spec/support/unified_backend_examples.rb +255 -0
- metadata +271 -0
@@ -0,0 +1,79 @@
|
|
1
|
+
|
2
|
+
require 'riak/client/http_backend'
|
3
|
+
require 'riak/failed_request'
|
4
|
+
|
5
|
+
module Riak
|
6
|
+
class Client
|
7
|
+
# Uses the Ruby standard library Net::HTTP to connect to Riak.
|
8
|
+
# Conforms to the Riak::Client::HTTPBackend interface.
|
9
|
+
class NetHTTPBackend < HTTPBackend
|
10
|
+
def self.configured?
|
11
|
+
begin
|
12
|
+
require 'net/http'
|
13
|
+
require 'openssl'
|
14
|
+
true
|
15
|
+
rescue LoadError, NameError
|
16
|
+
false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Sets the read_timeout applied to Net::HTTP connections
|
21
|
+
# Increase this if you have very long request times.
|
22
|
+
def self.read_timeout=(timeout)
|
23
|
+
@read_timeout = timeout
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.read_timeout
|
27
|
+
@read_timeout ||= 4096
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def perform(method, uri, headers, expect, data=nil) #:nodoc:
|
32
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
33
|
+
http.read_timeout = self.class.read_timeout
|
34
|
+
configure_ssl(http) if @client.ssl_enabled?
|
35
|
+
|
36
|
+
request = Net::HTTP.const_get(method.to_s.capitalize).new(uri.request_uri, headers)
|
37
|
+
case data
|
38
|
+
when String
|
39
|
+
request.body = data
|
40
|
+
when data.respond_to?(:read)
|
41
|
+
case
|
42
|
+
when data.respond_to?(:stat) # IO#stat
|
43
|
+
request.content_length = data.stat.size
|
44
|
+
when data.respond_to?(:size) # Some IO-like objects
|
45
|
+
request.content_length = data.size
|
46
|
+
else
|
47
|
+
request['Transfer-Encoding'] = 'chunked'
|
48
|
+
end
|
49
|
+
request.body_stream = data
|
50
|
+
end
|
51
|
+
|
52
|
+
{}.tap do |result|
|
53
|
+
http.request(request) do |response|
|
54
|
+
if valid_response?(expect, response.code)
|
55
|
+
result.merge!({:headers => response.to_hash, :code => response.code.to_i})
|
56
|
+
response.read_body {|chunk| yield chunk } if block_given?
|
57
|
+
if return_body?(method, response.code, block_given?)
|
58
|
+
result[:body] = response.body
|
59
|
+
end
|
60
|
+
else
|
61
|
+
raise Riak::HTTPFailedRequest.new(method, expect, response.code.to_i, response.to_hash, response.body)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def configure_ssl(http)
|
68
|
+
http.use_ssl = true
|
69
|
+
http.verify_mode = OpenSSL::SSL.const_get("VERIFY_#{@client.ssl_options[:verify_mode].upcase}")
|
70
|
+
if @client.ssl_options[:pem]
|
71
|
+
http.cert = OpenSSL::X509::Certificate.new(@client.ssl_options[:pem])
|
72
|
+
http.key = OpenSSL::PKey::RSA.new(@client.ssl_options[:pem], @client.ssl_options[:pem_password])
|
73
|
+
end
|
74
|
+
http.ca_file = @client.ssl_options[:ca_file] if @client.ssl_options[:ca_file]
|
75
|
+
http.ca_path = @client.ssl_options[:ca_path] if @client.ssl_options[:ca_path]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'riak'
|
2
|
+
require 'socket'
|
3
|
+
require 'base64'
|
4
|
+
require 'digest/sha1'
|
5
|
+
require 'riak/util/translation'
|
6
|
+
|
7
|
+
module Riak
|
8
|
+
class Client
|
9
|
+
class ProtobuffsBackend
|
10
|
+
include Util::Translation
|
11
|
+
|
12
|
+
# Message Codes Enum
|
13
|
+
MESSAGE_CODES = %W[
|
14
|
+
ErrorResp
|
15
|
+
PingReq
|
16
|
+
PingResp
|
17
|
+
GetClientIdReq
|
18
|
+
GetClientIdResp
|
19
|
+
SetClientIdReq
|
20
|
+
SetClientIdResp
|
21
|
+
GetServerInfoReq
|
22
|
+
GetServerInfoResp
|
23
|
+
GetReq
|
24
|
+
GetResp
|
25
|
+
PutReq
|
26
|
+
PutResp
|
27
|
+
DelReq
|
28
|
+
DelResp
|
29
|
+
ListBucketsReq
|
30
|
+
ListBucketsResp
|
31
|
+
ListKeysReq
|
32
|
+
ListKeysResp
|
33
|
+
GetBucketReq
|
34
|
+
GetBucketResp
|
35
|
+
SetBucketReq
|
36
|
+
SetBucketResp
|
37
|
+
MapRedReq
|
38
|
+
MapRedResp
|
39
|
+
].map {|s| s.intern }.freeze
|
40
|
+
|
41
|
+
def self.simple(method, code)
|
42
|
+
define_method method do
|
43
|
+
socket.write([1, MESSAGE_CODES.index(code)].pack('NC'))
|
44
|
+
decode_response
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
attr_accessor :client
|
49
|
+
def initialize(client)
|
50
|
+
@client = client
|
51
|
+
end
|
52
|
+
|
53
|
+
simple :ping, :PingReq
|
54
|
+
simple :get_client_id, :GetClientIdReq
|
55
|
+
simple :server_info, :GetServerInfoReq
|
56
|
+
simple :list_buckets, :ListBucketsReq
|
57
|
+
|
58
|
+
private
|
59
|
+
# Implemented by subclasses
|
60
|
+
def decode_response
|
61
|
+
raise NotImplementedError
|
62
|
+
end
|
63
|
+
|
64
|
+
def socket
|
65
|
+
Thread.current[:riakpbc_socket] ||= new_socket
|
66
|
+
end
|
67
|
+
|
68
|
+
def new_socket
|
69
|
+
socket = TCPSocket.new(@client.host, @client.pb_port)
|
70
|
+
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true)
|
71
|
+
socket
|
72
|
+
end
|
73
|
+
|
74
|
+
def reset_socket
|
75
|
+
socket.close
|
76
|
+
Thread.current[:riakpbc_socket] = nil
|
77
|
+
end
|
78
|
+
|
79
|
+
UINTMAX = 0xffffffff
|
80
|
+
QUORUMS = {
|
81
|
+
"one" => UINTMAX - 1,
|
82
|
+
"quorum" => UINTMAX - 2,
|
83
|
+
"all" => UINTMAX - 3,
|
84
|
+
"default" => UINTMAX - 4
|
85
|
+
}.freeze
|
86
|
+
|
87
|
+
def normalize_quorum_value(q)
|
88
|
+
QUORUMS[q.to_s] || q.to_i
|
89
|
+
end
|
90
|
+
|
91
|
+
# This doesn't give us exactly the keygen that Riak uses, but close.
|
92
|
+
def generate_key
|
93
|
+
Base64.encode64(Digest::SHA1.digest(Socket.gethostname + Time.now.iso8601(3))).tr("+/","-_").sub(/=+\n$/,'')
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
|
2
|
+
begin
|
3
|
+
require 'fiber'
|
4
|
+
rescue LoadError
|
5
|
+
require 'riak/util/fiber1.8'
|
6
|
+
end
|
7
|
+
|
8
|
+
module Riak
|
9
|
+
class Client
|
10
|
+
# @private
|
11
|
+
class Pump
|
12
|
+
def initialize(block)
|
13
|
+
@fiber = Fiber.new do
|
14
|
+
loop do
|
15
|
+
block.call Fiber.yield
|
16
|
+
end
|
17
|
+
end
|
18
|
+
@fiber.resume
|
19
|
+
end
|
20
|
+
|
21
|
+
def pump(input)
|
22
|
+
@fiber.resume input
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_proc
|
26
|
+
method(:pump).to_proc
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
|
2
|
+
require 'builder'
|
3
|
+
|
4
|
+
module Riak
|
5
|
+
class Client
|
6
|
+
# (Riak Search) Performs a search via the Solr interface.
|
7
|
+
# @overload search(index, query, options={})
|
8
|
+
# @param [String] index the index to query on
|
9
|
+
# @param [String] query a Lucene query string
|
10
|
+
# @overload search(query, options={})
|
11
|
+
# Queries the default index
|
12
|
+
# @param [String] query a Lucene query string
|
13
|
+
# @param [Hash] options extra options for the Solr query
|
14
|
+
# @option options [String] :df the default field to search in
|
15
|
+
# @option options [String] :'q.op' the default operator between terms ("or", "and")
|
16
|
+
# @option options [String] :wt ("json") the response type - "json" and "xml" are valid
|
17
|
+
# @option options [String] :sort ('none') the field and direction to sort, e.g. "name asc"
|
18
|
+
# @option options [Fixnum] :start (0) the offset into the query to start from, e.g. for pagination
|
19
|
+
# @option options [Fixnum] :rows (10) the number of results to return
|
20
|
+
# @return [Hash] the query result, containing the 'responseHeaders' and 'response' keys
|
21
|
+
def search(*args)
|
22
|
+
options = args.extract_options!
|
23
|
+
index, query = args[-2], args[-1] # Allows nil index, while keeping it as first argument
|
24
|
+
path = [solr, index, "select", {"q" => query, "wt" => "json"}.merge(options.stringify_keys), {}].compact
|
25
|
+
response = http.get(200, *path)
|
26
|
+
if response[:headers]['content-type'].include?("application/json")
|
27
|
+
JSON.parse(response[:body])
|
28
|
+
else
|
29
|
+
response[:body]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
alias :select :search
|
33
|
+
|
34
|
+
# (Riak Search) Adds documents to a search index via the Solr interface.
|
35
|
+
# @overload index(index, *docs)
|
36
|
+
# Adds documents to the specified search index
|
37
|
+
# @param [String] index the index in which to add/update the given documents
|
38
|
+
# @param [Array<Hash>] docs unnested document hashes, with one key per field
|
39
|
+
# @overload index(*docs)
|
40
|
+
# Adds documents to the default search index
|
41
|
+
# @param [Array<Hash>] docs unnested document hashes, with one key per field
|
42
|
+
# @raise [ArgumentError] if any documents don't include 'id' key
|
43
|
+
def index(*args)
|
44
|
+
index = args.shift if String === args.first # Documents must be hashes of fields
|
45
|
+
raise ArgumentError.new(t("search_docs_require_id")) unless args.all? {|d| d.key?("id") || d.key?(:id) }
|
46
|
+
xml = Builder::XmlMarkup.new
|
47
|
+
xml.add do
|
48
|
+
args.each do |doc|
|
49
|
+
xml.doc do
|
50
|
+
doc.each do |k,v|
|
51
|
+
xml.field('name' => k.to_s) { xml.text!(v.to_s) }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
path = [solr, index, "update", xml.target!, {'Content-Type' => 'text/xml'}].compact
|
57
|
+
http.post(200, *path)
|
58
|
+
true
|
59
|
+
end
|
60
|
+
alias :add_doc :index
|
61
|
+
|
62
|
+
# (Riak Search) Removes documents from a search index via the Solr interface.
|
63
|
+
# @overload remove(index, specs)
|
64
|
+
# Removes documents from the specified index
|
65
|
+
# @param [String] index the index from which to remove documents
|
66
|
+
# @param [Array<Hash>] specs the specificaiton of documents to remove (must contain 'id' or 'query' keys)
|
67
|
+
# @overload remove(specs)
|
68
|
+
# Removes documents from the default index
|
69
|
+
# @param [Array<Hash>] specs the specification of documents to remove (must contain 'id' or 'query' keys)
|
70
|
+
# @raise [ArgumentError] if any document specs don't include 'id' or 'query' keys
|
71
|
+
def remove(*args)
|
72
|
+
index = args.shift if String === args.first
|
73
|
+
raise ArgumentError.new(t("search_remove_requires_id_or_query")) unless args.all? { |s|
|
74
|
+
s.include? :id or
|
75
|
+
s.include? 'id' or
|
76
|
+
s.include? :query or
|
77
|
+
s.include? 'query'
|
78
|
+
}
|
79
|
+
xml = Builder::XmlMarkup.new
|
80
|
+
xml.delete do
|
81
|
+
args.each do |spec|
|
82
|
+
spec.each do |k,v|
|
83
|
+
xml.tag!(k.to_sym, v)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
path = [solr, index, "update", xml.target!, {'Content-Type' => 'text/xml'}].compact
|
88
|
+
http.post(200, *path)
|
89
|
+
true
|
90
|
+
end
|
91
|
+
alias :delete_doc :remove
|
92
|
+
alias :deindex :remove
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
unless Object.new.respond_to? :blank?
|
4
|
+
class Object
|
5
|
+
def blank?
|
6
|
+
false
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class NilClass
|
11
|
+
def blank?
|
12
|
+
true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class FalseClass
|
17
|
+
def blank?
|
18
|
+
true
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class TrueClass
|
23
|
+
def blank?
|
24
|
+
false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Set
|
29
|
+
alias :blank? :empty?
|
30
|
+
end
|
31
|
+
|
32
|
+
class String
|
33
|
+
def blank?
|
34
|
+
self !~ /[^\s]/
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class Array
|
39
|
+
alias :blank? :empty?
|
40
|
+
end
|
41
|
+
|
42
|
+
class Hash
|
43
|
+
alias :blank? :empty?
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
unless Object.new.respond_to? :present?
|
48
|
+
class Object
|
49
|
+
def present?
|
50
|
+
!blank?
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
unless {}.respond_to? :slice
|
2
|
+
class Hash
|
3
|
+
def slice(*keys)
|
4
|
+
allowed = Set.new(respond_to?(:convert_key) ? keys.map { |key| convert_key(key) } : keys)
|
5
|
+
hash = {}
|
6
|
+
allowed.each { |k| hash[k] = self[k] if has_key?(k) }
|
7
|
+
hash
|
8
|
+
end
|
9
|
+
|
10
|
+
def slice!(*keys)
|
11
|
+
keys = keys.map! { |key| convert_key(key) } if respond_to?(:convert_key)
|
12
|
+
omit = slice(*self.keys - keys)
|
13
|
+
hash = slice(*keys)
|
14
|
+
replace(hash)
|
15
|
+
omit
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
unless Object.new.respond_to? :to_query and Object.new.respond_to? :to_param
|
2
|
+
class Object
|
3
|
+
def to_param
|
4
|
+
to_s
|
5
|
+
end
|
6
|
+
|
7
|
+
def to_query(key)
|
8
|
+
require 'cgi' unless defined?(CGI) && defined?(CGI::escape)
|
9
|
+
"#{CGI.escape(key.to_s)}=#{CGI.escape(to_param.to_s)}"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Array
|
14
|
+
def to_param
|
15
|
+
map(&:to_param).join('/')
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_query(key)
|
19
|
+
prefix = "#{key}[]"
|
20
|
+
collect { |value| value.to_query(prefix) }.join '&'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Hash
|
25
|
+
def to_param(namespace = nil)
|
26
|
+
collect do |key, value|
|
27
|
+
value.to_query(namespace ? "#{namespace}[#{key}]" : key)
|
28
|
+
end.sort * '&'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|