better-riak-client 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. data/LICENSE +16 -0
  2. data/README.markdown +198 -0
  3. data/RELEASE_NOTES.md +211 -0
  4. data/better-riak-client.gemspec +61 -0
  5. data/erl_src/riak_kv_test014_backend.beam +0 -0
  6. data/erl_src/riak_kv_test014_backend.erl +189 -0
  7. data/erl_src/riak_kv_test_backend.beam +0 -0
  8. data/erl_src/riak_kv_test_backend.erl +697 -0
  9. data/erl_src/riak_search_test_backend.beam +0 -0
  10. data/erl_src/riak_search_test_backend.erl +175 -0
  11. data/lib/riak/bucket.rb +221 -0
  12. data/lib/riak/client/beefcake/messages.rb +213 -0
  13. data/lib/riak/client/beefcake/object_methods.rb +111 -0
  14. data/lib/riak/client/beefcake_protobuffs_backend.rb +226 -0
  15. data/lib/riak/client/decaying.rb +36 -0
  16. data/lib/riak/client/excon_backend.rb +162 -0
  17. data/lib/riak/client/feature_detection.rb +88 -0
  18. data/lib/riak/client/http_backend/configuration.rb +211 -0
  19. data/lib/riak/client/http_backend/key_streamer.rb +43 -0
  20. data/lib/riak/client/http_backend/object_methods.rb +106 -0
  21. data/lib/riak/client/http_backend/request_headers.rb +34 -0
  22. data/lib/riak/client/http_backend/transport_methods.rb +201 -0
  23. data/lib/riak/client/http_backend.rb +340 -0
  24. data/lib/riak/client/net_http_backend.rb +82 -0
  25. data/lib/riak/client/node.rb +115 -0
  26. data/lib/riak/client/protobuffs_backend.rb +173 -0
  27. data/lib/riak/client/search.rb +91 -0
  28. data/lib/riak/client.rb +540 -0
  29. data/lib/riak/cluster.rb +151 -0
  30. data/lib/riak/core_ext/blank.rb +53 -0
  31. data/lib/riak/core_ext/deep_dup.rb +13 -0
  32. data/lib/riak/core_ext/extract_options.rb +7 -0
  33. data/lib/riak/core_ext/json.rb +15 -0
  34. data/lib/riak/core_ext/slice.rb +18 -0
  35. data/lib/riak/core_ext/stringify_keys.rb +10 -0
  36. data/lib/riak/core_ext/symbolize_keys.rb +10 -0
  37. data/lib/riak/core_ext/to_param.rb +31 -0
  38. data/lib/riak/core_ext.rb +7 -0
  39. data/lib/riak/encoding.rb +6 -0
  40. data/lib/riak/failed_request.rb +81 -0
  41. data/lib/riak/i18n.rb +5 -0
  42. data/lib/riak/json.rb +52 -0
  43. data/lib/riak/link.rb +94 -0
  44. data/lib/riak/locale/en.yml +53 -0
  45. data/lib/riak/locale/fr.yml +52 -0
  46. data/lib/riak/map_reduce/filter_builder.rb +103 -0
  47. data/lib/riak/map_reduce/phase.rb +98 -0
  48. data/lib/riak/map_reduce.rb +225 -0
  49. data/lib/riak/map_reduce_error.rb +7 -0
  50. data/lib/riak/node/configuration.rb +293 -0
  51. data/lib/riak/node/console.rb +133 -0
  52. data/lib/riak/node/control.rb +207 -0
  53. data/lib/riak/node/defaults.rb +83 -0
  54. data/lib/riak/node/generation.rb +106 -0
  55. data/lib/riak/node/log.rb +34 -0
  56. data/lib/riak/node/version.rb +43 -0
  57. data/lib/riak/node.rb +38 -0
  58. data/lib/riak/robject.rb +318 -0
  59. data/lib/riak/search.rb +3 -0
  60. data/lib/riak/serializers.rb +74 -0
  61. data/lib/riak/stamp.rb +77 -0
  62. data/lib/riak/test_server.rb +89 -0
  63. data/lib/riak/util/escape.rb +76 -0
  64. data/lib/riak/util/headers.rb +53 -0
  65. data/lib/riak/util/multipart/stream_parser.rb +62 -0
  66. data/lib/riak/util/multipart.rb +52 -0
  67. data/lib/riak/util/tcp_socket_extensions.rb +58 -0
  68. data/lib/riak/util/translation.rb +19 -0
  69. data/lib/riak/version.rb +3 -0
  70. data/lib/riak/walk_spec.rb +105 -0
  71. data/lib/riak.rb +21 -0
  72. metadata +348 -0
@@ -0,0 +1,3 @@
1
+ require 'riak'
2
+
3
+ warn Riak.t('deprecated.search', :backtrace => " "+caller.join("\n "))
@@ -0,0 +1,74 @@
1
+ module Riak
2
+ module Serializers
3
+ include Util::Translation
4
+ extend self
5
+
6
+ def [](content_type)
7
+ serializers[content_type]
8
+ end
9
+
10
+ def []=(content_type, serializer)
11
+ serializers[content_type] = serializer
12
+ end
13
+
14
+ def serialize(content_type, content)
15
+ serializer_for(content_type).dump(content)
16
+ end
17
+
18
+ def deserialize(content_type, content)
19
+ serializer_for(content_type).load(content)
20
+ end
21
+
22
+ private
23
+
24
+ def serializer_for(content_type)
25
+ serializers.fetch(content_type[/^[^;\s]+/]) do
26
+ raise NotImplementedError.new(t('serializer_not_implemented', :content_type => content_type.inspect))
27
+ end
28
+ end
29
+
30
+ def serializers
31
+ @serializers ||= {}
32
+ end
33
+
34
+ module TextPlain
35
+ extend self
36
+
37
+ def dump(object)
38
+ object.to_s
39
+ end
40
+
41
+ def load(string)
42
+ string
43
+ end
44
+ end
45
+
46
+ module ApplicationJSON
47
+ extend self
48
+
49
+ def dump(object)
50
+ object.to_json(Riak.json_options)
51
+ end
52
+
53
+ def load(string)
54
+ Riak::JSON.parse(string)
55
+ end
56
+ end
57
+
58
+ Serializers['text/plain'] = TextPlain
59
+ Serializers['application/json'] = ApplicationJSON
60
+ Serializers['application/x-ruby-marshal'] = ::Marshal
61
+
62
+ YAML_MIME_TYPES = %w[
63
+ text/yaml
64
+ text/x-yaml
65
+ application/yaml
66
+ application/x-yaml
67
+ ]
68
+
69
+ YAML_MIME_TYPES.each do |mime_type|
70
+ Serializers[mime_type] = ::YAML
71
+ end
72
+ end
73
+ end
74
+
data/lib/riak/stamp.rb ADDED
@@ -0,0 +1,77 @@
1
+ require 'riak/client'
2
+ require 'riak/util/translation'
3
+ require 'thread'
4
+
5
+ module Riak
6
+ # Implements a client-side form of monotonically-increasing k-sorted
7
+ # unique identifiers. These are useful for key generation if your
8
+ # data is time-sequential and needs to be sorted by key, perhaps in
9
+ # Riak Search. Inspired by Twitter's Snowflake project.
10
+ class Stamp
11
+ attr_reader :client
12
+
13
+ CLIENT_ID_MASK = (1 << 10) - 1
14
+ SEQUENCE_MASK = (1 << 12) - 1
15
+ TIMESTAMP_MASK = (1 << 41) - 1
16
+ SEQUENCE_SHIFT = 10
17
+ TIMESTAMP_SHIFT = 22
18
+
19
+ # @param [Client] client a {Riak::Client} which will be used for
20
+ # the "worker ID" component of the stamp.
21
+ # @see Client#stamp
22
+ def initialize(client)
23
+ @client = client
24
+ @mutex = Mutex.new
25
+ @timestamp = time_gen
26
+ @sequence = 0
27
+ end
28
+
29
+ # Generates a k-sorted unique ID for use as a key or other
30
+ # disambiguation purposes.
31
+ def next
32
+ @mutex.synchronize do
33
+ now = time_gen
34
+ if @timestamp == now
35
+ @sequence = (@sequence + 1) & SEQUENCE_MASK
36
+ now = wait_for_next_ms(@timestamp) if @sequence == 0
37
+ else
38
+ @sequence = 0
39
+ end
40
+
41
+ raise BackwardsClockError.new(@timestamp - now) if now < @timestamp
42
+
43
+ @timestamp = now
44
+ @timestamp << TIMESTAMP_SHIFT | @sequence << SEQUENCE_SHIFT | client_id
45
+ end
46
+ end
47
+
48
+ private
49
+ def client_id
50
+ case id = @client.client_id
51
+ when Integer
52
+ id & CLIENT_ID_MASK
53
+ else
54
+ id.hash & CLIENT_ID_MASK
55
+ end
56
+ end
57
+
58
+ def time_gen
59
+ (Time.now.to_f * 1000).floor & TIMESTAMP_MASK
60
+ end
61
+
62
+ def wait_for_next_ms(start)
63
+ now = time_gen
64
+ now = time_gen while now <= start
65
+ now
66
+ end
67
+ end
68
+
69
+ # Raised when calling {Stamp#next} and NTP or some other external
70
+ # event has moved the system clock backwards.
71
+ class BackwardsClockError < StandardError
72
+ include Util::Translation
73
+ def initialize(delay)
74
+ super t('backwards_clock', :delay => delay)
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,89 @@
1
+ require 'riak/node'
2
+
3
+ if ENV['DEBUG_RIAK_TEST_SERVER']
4
+ $expect_verbose = true
5
+ end
6
+
7
+ module Riak
8
+ # The TestServer is a special {Node} that uses in-memory storage
9
+ # engines that are easily cleared. This is helpful when running test
10
+ # suites that store and retrieve objects from Riak and expect a
11
+ # clean-slate at the beginning of each test. Like {Node}, creation
12
+ # is idempotent, so you can keep the server around between runs of
13
+ # your test suite.
14
+ class TestServer < Node
15
+ # Creates a TestServer node, using in-memory backends for KV and Search.
16
+ def initialize(configuration = {})
17
+ configuration[:env] ||= {}
18
+ configuration[:env][:riak_kv] ||= {}
19
+ (configuration[:env][:riak_kv][:add_paths] ||= []) << File.expand_path("../../../erl_src", __FILE__)
20
+ configuration[:env][:riak_kv][:test] = true
21
+ configuration[:env][:memory_backend] ||={}
22
+ configuration[:env][:memory_backend][:test] = true
23
+ configuration[:env][:riak_search] ||= {}
24
+ configuration[:env][:riak_search][:search_backend] = :riak_search_test_backend
25
+ super configuration
26
+ end
27
+
28
+ # Overrides the default {Node#started?} to simply return true if the
29
+ # console is still attached.
30
+ def started?
31
+ open? || super
32
+ end
33
+
34
+ # Overrides the default {Node#start} to return early if the
35
+ # console is still attached. Otherwise, starts and immediately
36
+ # attaches the console.
37
+ def start
38
+ unless open?
39
+ super
40
+ maybe_attach
41
+ end
42
+ end
43
+
44
+ # Overrides the default {Node#stop} to close the console before
45
+ # stopping the node.
46
+ def stop
47
+ @console.close if @console && !@console.frozen?
48
+ @console = nil
49
+ super
50
+ end
51
+
52
+ # Overrides the default {Node#drop} to simply clear the in-memory
53
+ # backends.
54
+ def drop
55
+ begin
56
+ maybe_attach
57
+ @console.command "#{kv_backend}:reset()."
58
+ @console.command "riak_search_test_backend:reset()."
59
+ rescue IOError
60
+ retry
61
+ end
62
+ end
63
+
64
+ protected
65
+ # Tries to reattach the console if it's closed
66
+ def maybe_attach
67
+ unless open?
68
+ @console.close if @console && !@console.frozen?
69
+ @console = attach
70
+ end
71
+ end
72
+
73
+ def open?
74
+ @console && @console.open?
75
+ end
76
+
77
+ def configure_data
78
+ super
79
+ if version < "1.0.0"
80
+ env[:riak_kv][:storage_backend] = :riak_kv_test014_backend
81
+ elsif version =~ /^1\.[01]\.\d+$/ # 1.0 and 1.1 series
82
+ env[:riak_kv][:storage_backend] = :riak_kv_test_backend
83
+ else
84
+ # TODO: change this when 1.2+ is released, if it includes riak_kv#314
85
+ env[:riak_kv][:storage_backend] = :riak_kv_memory_backend
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,76 @@
1
+ require 'cgi'
2
+ require 'uri'
3
+
4
+ module Riak
5
+ class << self
6
+ # @see #escaper=
7
+ attr_reader :escaper
8
+
9
+ # Sets the class used for escaping URLs (buckets and keys) sent to
10
+ # Riak. Currently only supports URI and CGI, and defaults to URI.
11
+ # @param [Symbol,String,Class] esc A representation of which
12
+ # escaping class to use, either the Class itself or a String or
13
+ # Symbol name
14
+ # @see Riak::Util::Escape
15
+ def escaper=(esc)
16
+ case esc
17
+ when Symbol, String
18
+ @escaper = ::Object.const_get(esc.to_s.upcase.intern) if esc.to_s =~ /uri|cgi/i
19
+ when Class, Module
20
+ @escaper = esc
21
+ end
22
+ end
23
+
24
+ # In Riak 1.0+, buckets and keys are decoded internally before
25
+ # being stored. This increases compatibility with the Protocol
26
+ # Buffers transport and reduces inconsistency of link-walking
27
+ # vs. regular operations. If the node you are connecting to has
28
+ # set {http_url_encoding, on}, set this to true. Default is false.
29
+ # @return [true,false] Whether Riak decodes URL-encoded paths and headers
30
+ attr_accessor :url_decoding
31
+ end
32
+
33
+ self.escaper = URI
34
+ self.url_decoding = false
35
+
36
+ module Util
37
+ # Methods for escaping URL segments.
38
+ module Escape
39
+ # Conditionally escapes buckets and keys depending on whether
40
+ # Riak is configured to decode them. This is used in situations
41
+ # where the bucket or key is not part of a URL, but would need
42
+ # to be escaped on Riak 0.14 and earlier so that the name
43
+ # matches.
44
+ # @param [String] bucket_or_key the bucket or key name
45
+ # @return [String] the escaped path segment
46
+ def maybe_escape(bucket_or_key)
47
+ Riak.url_decoding ? bucket_or_key : escape(bucket_or_key)
48
+ end
49
+
50
+ # Escapes bucket or key names that may contain slashes for use in URLs.
51
+ # @param [String] bucket_or_key the bucket or key name
52
+ # @return [String] the escaped path segment
53
+ def escape(bucket_or_key)
54
+ Riak.escaper.escape(bucket_or_key.to_s).gsub("+", "%20").gsub('/', "%2F")
55
+ end
56
+
57
+ # Conditionally unescapes buckets and keys depending on whether
58
+ # Riak is configured to decode them. This is used in situations
59
+ # where the bucket or key is not part of a URL, but would need
60
+ # to be escaped on Riak 0.14 and earlier so that the name
61
+ # matches.
62
+ # @param [String] bucket_or_key the escaped bucket or key name
63
+ # @return [String] the unescaped path segment
64
+ def maybe_unescape(bucket_or_key)
65
+ Riak.url_decoding ? bucket_or_key : unescape(bucket_or_key)
66
+ end
67
+
68
+ # Unescapes bucket or key names in URLs.
69
+ # @param [String] bucket_or_key the bucket or key name
70
+ # @return [String] the unescaped name
71
+ def unescape(bucket_or_key)
72
+ Riak.escaper.unescape(bucket_or_key.to_s)
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,53 @@
1
+ require 'net/http'
2
+
3
+ # Splits headers into < 8KB chunks
4
+ # @private
5
+ module Net::HTTPHeader
6
+ def each_capitalized
7
+ # 1.9 check
8
+ respond_to?(:enum_for) and (block_given? or return enum_for(__method__))
9
+ @header.each do |k,v|
10
+ base_length = "#{k}: \r\n".length
11
+ values = v.map {|i| i.to_s.split(", ") }.flatten
12
+ while !values.empty?
13
+ current_line = ""
14
+ while values.first && current_line.length + base_length + values.first.length + 2 < 8192
15
+ val = values.shift.strip
16
+ current_line += current_line.empty? ? val : ", #{val}"
17
+ end
18
+ yield capitalize(k), current_line
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ module Riak
25
+ module Util
26
+ # Represents headers from an HTTP request or response.
27
+ # Used internally by HTTP backends for processing headers.
28
+ class Headers
29
+ include Net::HTTPHeader
30
+
31
+ def initialize
32
+ initialize_http_header({})
33
+ end
34
+
35
+ # Parse a single header line into its key and value
36
+ # @param [String] chunk a single header line
37
+ def self.parse(chunk)
38
+ line = chunk.strip
39
+ # thanks Net::HTTPResponse
40
+ return [nil,nil] if chunk =~ /\AHTTP(?:\/(\d+\.\d+))?\s+(\d\d\d)\s*(.*)\z/in
41
+ m = /\A([^:]+):\s*/.match(line)
42
+ [m[1], m.post_match] rescue [nil, nil]
43
+ end
44
+
45
+ # Parses a header line and adds it to the header collection
46
+ # @param [String] chunk a single header line
47
+ def parse(chunk)
48
+ key, value = self.class.parse(chunk)
49
+ add_field(key, value) if key && value
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,62 @@
1
+ require 'riak/util/translation'
2
+ require 'riak/util/multipart'
3
+
4
+ module Riak
5
+ module Util
6
+ module Multipart
7
+ # This class is parses chunked/streamed multipart HTTP
8
+ # streams. It is used by streaming MapReduce queries, and in the
9
+ # future streaming key-lists (once implemented on the Riak side).
10
+ class StreamParser
11
+ include Multipart
12
+ include Translation
13
+ # Creates a new StreamParser.
14
+ #
15
+ # Example usage:
16
+ # http.get(200, "/riak", "foo", {}, &StreamParser.new {|part| ... })
17
+ #
18
+ # @yield [Hash] parts of the multipart/mixed stream,
19
+ # containing :headers and :body keys
20
+ def initialize(&block)
21
+ raise ArgumentError, t('missing_block') unless block_given?
22
+ @buffer = ""
23
+ @block = block
24
+ @state = :get_boundary
25
+ end
26
+
27
+ # Accepts a chunk of the HTTP response stream, and yields to
28
+ # the block when appropriate.
29
+ def accept(chunk)
30
+ @buffer << chunk
31
+ @state = send(@state)
32
+ end
33
+
34
+ # Returns a Proc that can be passed to an HTTP request method.
35
+ def to_proc
36
+ method(:accept).to_proc
37
+ end
38
+
39
+ private
40
+ CAPTURE_BOUNDARY = /^--([A-Za-z0-9\'()+_,-.\/:=?]+)\r?\n/
41
+
42
+ def get_boundary
43
+ if @buffer =~ CAPTURE_BOUNDARY
44
+ @re = /\r?\n--#{Regexp.escape($1)}(?:--)?\r?\n/
45
+ @buffer = $~.post_match
46
+ buffering
47
+ else
48
+ :get_boundary
49
+ end
50
+ end
51
+
52
+ def buffering
53
+ while @buffer =~ @re
54
+ @block.call parse_multipart_section($~.pre_match)
55
+ @buffer = $~.post_match
56
+ end
57
+ :buffering
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,52 @@
1
+ require 'riak/util/headers'
2
+
3
+ module Riak
4
+ module Util
5
+ # Utility methods for handling multipart/mixed responses
6
+ module Multipart
7
+ extend self
8
+ # Parses a multipart/mixed body into its constituent parts, including nested multipart/mixed sections
9
+ # @param [String] data the multipart body data
10
+ # @param [String] boundary the boundary string given in the Content-Type header
11
+ def parse(data, boundary)
12
+ contents = data.match(end_boundary_regex(boundary)).pre_match rescue ""
13
+ contents.split(inner_boundary_regex(boundary)).reject(&:blank?).map do |part|
14
+ parse_multipart_section(part)
15
+ end.compact
16
+ end
17
+
18
+ # Extracts the boundary string from a Content-Type header that is a multipart type
19
+ # @param [String] header_string the Content-Type header
20
+ # @return [String] the boundary string separating each part
21
+ def extract_boundary(header_string)
22
+ $1 if header_string =~ /boundary=([A-Za-z0-9\'()+_,-.\/:=?]+)/
23
+ end
24
+
25
+ private
26
+ def end_boundary_regex(boundary)
27
+ /\r?\n--#{Regexp.escape(boundary)}--\r?\n/
28
+ end
29
+
30
+ def inner_boundary_regex(boundary)
31
+ /\r?\n--#{Regexp.escape(boundary)}\r?\n/
32
+ end
33
+
34
+ def parse_multipart_section(part)
35
+ headers = Headers.new
36
+ if md = part.match(/\r?\n\r?\n/)
37
+ body = md.post_match
38
+ md.pre_match.split(/\r?\n/).each do |line|
39
+ headers.parse(line)
40
+ end
41
+
42
+ if headers["content-type"] =~ /multipart\/mixed/
43
+ boundary = extract_boundary(headers.to_hash["content-type"].first)
44
+ parse(body, boundary)
45
+ else
46
+ {:headers => headers.to_hash, :body => body}
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end