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.
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