riak-client 0.9.8 → 1.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. data/.gitignore +32 -0
  2. data/Gemfile +17 -11
  3. data/Guardfile +14 -0
  4. data/Rakefile +18 -44
  5. data/erl_src/riak_kv_test_backend.beam +0 -0
  6. data/erl_src/riak_kv_test_backend.erl +461 -128
  7. data/erl_src/riak_search_test_backend.beam +0 -0
  8. data/erl_src/riak_search_test_backend.erl +175 -0
  9. data/lib/active_support/cache/riak_store.rb +0 -13
  10. data/lib/riak.rb +11 -16
  11. data/lib/riak/bucket.rb +59 -41
  12. data/lib/riak/cache_store.rb +1 -14
  13. data/lib/riak/client.rb +145 -73
  14. data/lib/riak/client/beefcake/messages.rb +36 -31
  15. data/lib/riak/client/beefcake/object_methods.rb +27 -19
  16. data/lib/riak/client/beefcake_protobuffs_backend.rb +27 -33
  17. data/lib/riak/client/excon_backend.rb +0 -13
  18. data/lib/riak/client/http_backend.rb +95 -60
  19. data/lib/riak/client/http_backend/configuration.rb +144 -19
  20. data/lib/riak/client/http_backend/key_streamer.rb +1 -14
  21. data/lib/riak/client/http_backend/object_methods.rb +16 -16
  22. data/lib/riak/client/http_backend/request_headers.rb +0 -13
  23. data/lib/riak/client/http_backend/transport_methods.rb +26 -56
  24. data/lib/riak/client/net_http_backend.rb +11 -13
  25. data/lib/riak/client/protobuffs_backend.rb +21 -19
  26. data/lib/riak/client/pump.rb +1 -15
  27. data/lib/riak/client/search.rb +85 -0
  28. data/lib/riak/cluster.rb +151 -0
  29. data/lib/riak/core_ext.rb +1 -0
  30. data/lib/riak/core_ext/deep_dup.rb +13 -0
  31. data/lib/riak/core_ext/json.rb +15 -0
  32. data/lib/riak/core_ext/stringify_keys.rb +1 -1
  33. data/lib/riak/core_ext/symbolize_keys.rb +1 -1
  34. data/lib/riak/encoding.rb +6 -0
  35. data/lib/riak/failed_request.rb +2 -15
  36. data/lib/riak/i18n.rb +0 -13
  37. data/lib/riak/json.rb +19 -8
  38. data/lib/riak/link.rb +18 -20
  39. data/lib/riak/locale/en.yml +13 -16
  40. data/lib/riak/map_reduce.rb +40 -20
  41. data/lib/riak/map_reduce/filter_builder.rb +14 -18
  42. data/lib/riak/map_reduce/phase.rb +0 -13
  43. data/lib/riak/map_reduce_error.rb +0 -13
  44. data/lib/riak/node.rb +38 -0
  45. data/lib/riak/node/configuration.rb +286 -0
  46. data/lib/riak/node/console.rb +139 -0
  47. data/lib/riak/node/control.rb +207 -0
  48. data/lib/riak/node/defaults.rb +70 -0
  49. data/lib/riak/node/generation.rb +99 -0
  50. data/lib/riak/node/log.rb +34 -0
  51. data/lib/riak/node/version.rb +37 -0
  52. data/lib/riak/robject.rb +45 -41
  53. data/lib/riak/search.rb +2 -161
  54. data/lib/riak/serializers.rb +74 -0
  55. data/lib/riak/stamp.rb +77 -0
  56. data/lib/riak/test_server.rb +56 -220
  57. data/lib/riak/util/escape.rb +58 -17
  58. data/lib/riak/util/headers.rb +2 -15
  59. data/lib/riak/util/multipart.rb +0 -13
  60. data/lib/riak/util/multipart/stream_parser.rb +0 -13
  61. data/lib/riak/util/tcp_socket_extensions.rb +1 -14
  62. data/lib/riak/util/translation.rb +0 -13
  63. data/lib/riak/version.rb +3 -0
  64. data/lib/riak/walk_spec.rb +0 -13
  65. data/riak-client.gemspec +27 -47
  66. data/spec/fixtures/multipart-with-marked-tombstones.txt +17 -0
  67. data/spec/fixtures/multipart-with-unmarked-tombstone.txt +16 -0
  68. data/spec/integration/riak/cache_store_spec.rb +2 -40
  69. data/spec/integration/riak/cluster_spec.rb +88 -0
  70. data/spec/integration/riak/http_backends_spec.rb +6 -30
  71. data/spec/integration/riak/node_spec.rb +184 -0
  72. data/spec/integration/riak/protobuffs_backends_spec.rb +2 -26
  73. data/spec/integration/riak/test_server_spec.rb +31 -167
  74. data/spec/riak/beefcake_protobuffs_backend_spec.rb +5 -4
  75. data/spec/riak/bucket_spec.rb +26 -36
  76. data/spec/riak/client_spec.rb +44 -38
  77. data/spec/riak/escape_spec.rb +56 -30
  78. data/spec/riak/excon_backend_spec.rb +4 -17
  79. data/spec/riak/headers_spec.rb +1 -14
  80. data/spec/riak/http_backend/configuration_spec.rb +211 -34
  81. data/spec/riak/http_backend/object_methods_spec.rb +52 -18
  82. data/spec/riak/http_backend/transport_methods_spec.rb +5 -38
  83. data/spec/riak/http_backend_spec.rb +84 -78
  84. data/spec/riak/link_spec.rb +19 -18
  85. data/spec/riak/map_reduce/filter_builder_spec.rb +1 -14
  86. data/spec/riak/map_reduce/phase_spec.rb +1 -14
  87. data/spec/riak/map_reduce_spec.rb +141 -43
  88. data/spec/riak/multipart_spec.rb +1 -14
  89. data/spec/riak/net_http_backend_spec.rb +2 -15
  90. data/spec/riak/robject_spec.rb +129 -97
  91. data/spec/riak/search_spec.rb +45 -62
  92. data/spec/riak/serializers_spec.rb +93 -0
  93. data/spec/riak/stamp_spec.rb +54 -0
  94. data/spec/riak/stream_parser_spec.rb +3 -16
  95. data/spec/riak/walk_spec_spec.rb +1 -14
  96. data/spec/spec_helper.rb +22 -27
  97. data/spec/support/http_backend_implementation_examples.rb +49 -79
  98. data/spec/support/integration_setup.rb +10 -0
  99. data/spec/support/mock_server.rb +0 -14
  100. data/spec/support/mocks.rb +0 -13
  101. data/spec/support/test_server.rb +30 -0
  102. data/spec/support/test_server.yml.example +14 -2
  103. data/spec/support/unified_backend_examples.rb +36 -27
  104. metadata +100 -31
  105. data/lib/riak/client/curb_backend.rb +0 -89
  106. data/spec/riak/curb_backend_spec.rb +0 -76
@@ -1,16 +1,3 @@
1
- # Copyright 2010 Sean Cribbs, Sonian Inc., and Basho Technologies, Inc.
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
1
  require 'riak'
15
2
  require 'socket'
16
3
  require 'base64'
@@ -68,6 +55,18 @@ module Riak
68
55
  simple :server_info, :GetServerInfoReq
69
56
  simple :list_buckets, :ListBucketsReq
70
57
 
58
+ # Performs a secondary-index query via emulation through MapReduce.
59
+ # @param [String, Bucket] bucket the bucket to query
60
+ # @param [String] index the index to query
61
+ # @param [String, Integer, Range] query the equality query or
62
+ # range query to perform
63
+ # @return [Array<String>] a list of keys matching the query
64
+ def get_index(bucket, index, query)
65
+ mapred(Riak::MapReduce.new(client).
66
+ index(bucket, index, query).
67
+ reduce(%w[riak_kv_mapreduce reduce_identity], :arg => {:reduce_phase_only_1 => true}, :keep => true)).map {|p| p.last }
68
+ end
69
+
71
70
  private
72
71
  # Implemented by subclasses
73
72
  def decode_response
@@ -85,7 +84,7 @@ module Riak
85
84
  end
86
85
 
87
86
  def reset_socket
88
- socket.close
87
+ socket.close if !socket.closed?
89
88
  Thread.current[:riakpbc_socket] = nil
90
89
  end
91
90
 
@@ -97,13 +96,16 @@ module Riak
97
96
  "default" => UINTMAX - 4
98
97
  }.freeze
99
98
 
100
- def normalize_quorum_value(q)
101
- QUORUMS[q.to_s] || q.to_i
99
+ def normalize_quorums(options={})
100
+ options.dup.tap do |o|
101
+ [:r, :pr, :w, :pw, :dw, :rw].each do |k|
102
+ o[k] = normalize_quorum_value(o[k]) if o[k]
103
+ end
104
+ end
102
105
  end
103
106
 
104
- # This doesn't give us exactly the keygen that Riak uses, but close.
105
- def generate_key
106
- Base64.encode64(Digest::SHA1.digest(Socket.gethostname + Time.now.iso8601(3))).tr("+/","-_").sub(/=+\n$/,'')
107
+ def normalize_quorum_value(q)
108
+ QUORUMS[q.to_s] || q.to_i
107
109
  end
108
110
  end
109
111
  end
@@ -1,16 +1,3 @@
1
- # Copyright 2010 Sean Cribbs, Sonian Inc., and Basho Technologies, Inc.
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
1
 
15
2
  begin
16
3
  require 'fiber'
@@ -21,7 +8,7 @@ end
21
8
  module Riak
22
9
  class Client
23
10
  # @private
24
- class Pump
11
+ class Pump
25
12
  def initialize(block)
26
13
  @fiber = Fiber.new do
27
14
  loop do
@@ -33,7 +20,6 @@ module Riak
33
20
 
34
21
  def pump(input)
35
22
  @fiber.resume input
36
- input.size if input.respond_to?(:size) # for curb
37
23
  end
38
24
 
39
25
  def to_proc
@@ -0,0 +1,85 @@
1
+ require 'builder'
2
+
3
+ module Riak
4
+ class Client
5
+ # (Riak Search) Performs a search via the Solr interface.
6
+ # @overload search(index, query, options={})
7
+ # @param [String] index the index to query on
8
+ # @param [String] query a Lucene query string
9
+ # @overload search(query, options={})
10
+ # Queries the default index
11
+ # @param [String] query a Lucene query string
12
+ # @param [Hash] options extra options for the Solr query
13
+ # @option options [String] :df the default field to search in
14
+ # @option options [String] :'q.op' the default operator between terms ("or", "and")
15
+ # @option options [String] :wt ("json") the response type - "json" and "xml" are valid
16
+ # @option options [String] :sort ('none') the field and direction to sort, e.g. "name asc"
17
+ # @option options [Fixnum] :start (0) the offset into the query to start from, e.g. for pagination
18
+ # @option options [Fixnum] :rows (10) the number of results to return
19
+ # @return [Hash] the query result, containing the 'responseHeaders' and 'response' keys
20
+ def search(*args)
21
+ options = args.extract_options!
22
+ index, query = args[-2], args[-1] # Allows nil index, while keeping it as firstargument
23
+ http.search(index, query, options)
24
+ end
25
+ alias :select :search
26
+
27
+ # (Riak Search) Adds documents to a search index via the Solr interface.
28
+ # @overload index(index, *docs)
29
+ # Adds documents to the specified search index
30
+ # @param [String] index the index in which to add/update the given documents
31
+ # @param [Array<Hash>] docs unnested document hashes, with one key per field
32
+ # @overload index(*docs)
33
+ # Adds documents to the default search index
34
+ # @param [Array<Hash>] docs unnested document hashes, with one key per field
35
+ # @raise [ArgumentError] if any documents don't include 'id' key
36
+ def index(*args)
37
+ index = args.shift if String === args.first # Documents must be hashes of fields
38
+ raise ArgumentError.new(t("search_docs_require_id")) unless args.all? {|d| d.key?("id") || d.key?(:id) }
39
+ xml = Builder::XmlMarkup.new
40
+ xml.add do
41
+ args.each do |doc|
42
+ xml.doc do
43
+ doc.each do |k,v|
44
+ xml.field('name' => k.to_s) { xml.text!(v.to_s) }
45
+ end
46
+ end
47
+ end
48
+ end
49
+ http.update_search_index(index, xml.target!)
50
+ true
51
+ end
52
+ alias :add_doc :index
53
+
54
+ # (Riak Search) Removes documents from a search index via the Solr interface.
55
+ # @overload remove(index, specs)
56
+ # Removes documents from the specified index
57
+ # @param [String] index the index from which to remove documents
58
+ # @param [Array<Hash>] specs the specificaiton of documents to remove (must contain 'id' or 'query' keys)
59
+ # @overload remove(specs)
60
+ # Removes documents from the default index
61
+ # @param [Array<Hash>] specs the specification of documents to remove (must contain 'id' or 'query' keys)
62
+ # @raise [ArgumentError] if any document specs don't include 'id' or 'query' keys
63
+ def remove(*args)
64
+ index = args.shift if String === args.first
65
+ raise ArgumentError.new(t("search_remove_requires_id_or_query")) unless args.all? { |s|
66
+ s.include? :id or
67
+ s.include? 'id' or
68
+ s.include? :query or
69
+ s.include? 'query'
70
+ }
71
+ xml = Builder::XmlMarkup.new
72
+ xml.delete do
73
+ args.each do |spec|
74
+ spec.each do |k,v|
75
+ xml.tag!(k.to_sym, v)
76
+ end
77
+ end
78
+ end
79
+ http.update_search_index(index, xml.target!)
80
+ true
81
+ end
82
+ alias :delete_doc :remove
83
+ alias :deindex :remove
84
+ end
85
+ end
@@ -0,0 +1,151 @@
1
+ require 'pathname'
2
+ require 'riak/node'
3
+ require 'riak/util/translation'
4
+
5
+ module Riak
6
+ # Generates and controls a cluster of {Riak::Node} instances for use
7
+ # in development or testing on a single machine.
8
+ class Cluster
9
+ include Util::Translation
10
+ # @return [Array<Node>] the member Nodes of this cluster
11
+ attr_reader :nodes
12
+
13
+ # @return [Hash] the cluster configuration
14
+ attr_reader :configuration
15
+
16
+ # @return [Pathname] the root directory of the cluster
17
+ attr_reader :root
18
+
19
+ # Creates a {Cluster} of {Node}s.
20
+ # @param [Hash] config the configuration for the cluster
21
+ # @option config [Fixnum] :count the number of nodes to create
22
+ # @option config [String] :source path to the Riak bin/ directory.
23
+ # See {Node#source}.
24
+ # @option config [String] :root path to where the nodes will be
25
+ # generated.
26
+ # @option config [Fixnum] :min_port the base port number from
27
+ # which nodes will claim IP ports for HTTP, PB, handoff.
28
+ def initialize(config={})
29
+ raise ArgumentError, t('source_and_root_required') unless config[:source] && config[:root]
30
+ @configuration = config
31
+ @count = config.delete(:count) || 4
32
+ @min_port = config.delete(:min_port) || 9000
33
+ @root = Pathname.new(config.delete(:root))
34
+ @nodes = []
35
+ cookie = "#{rand(100000).to_s}_#{rand(1000000).to_s}"
36
+ @count.times do |i|
37
+ nodes << Riak::Node.new(config.merge(:min_port => @min_port + (i * 3),
38
+ :root => @root + (i+1).to_s,
39
+ :cookie => cookie))
40
+ end
41
+ end
42
+
43
+ # @return [true,false] whether the cluster has been created
44
+ def exist?
45
+ root.directory? && nodes.all? {|n| n.exist? }
46
+ end
47
+
48
+ # Generates all nodes in the cluster.
49
+ def create
50
+ unless exist?
51
+ root.mkpath unless root.exist?
52
+ nodes.each {|n| n.create }
53
+ end
54
+ end
55
+
56
+ # Removes all nodes in the cluster and the root, and freezes the
57
+ # object.
58
+ def destroy
59
+ nodes.each {|n| n.destroy }
60
+ root.rmtree if root.exist?
61
+ freeze
62
+ end
63
+
64
+ # Removes and recreates the cluster.
65
+ def recreate
66
+ stop unless stopped?
67
+ root.rmtree if root.exist?
68
+ create
69
+ end
70
+
71
+ # Drops all data from the cluster without destroying the nodes.
72
+ def drop
73
+ nodes.each {|n| n.drop }
74
+ end
75
+
76
+ # Starts all nodes in the cluster.
77
+ def start
78
+ nodes.each {|n| n.start }
79
+ end
80
+
81
+ # Stops all nodes in the cluster.
82
+ def stop
83
+ nodes.each {|n| n.stop }
84
+ end
85
+
86
+ # Restarts all nodes in the cluster (without exiting the Erlang
87
+ # runtime)
88
+ def restart
89
+ nodes.each {|n| n.restart }
90
+ end
91
+
92
+ # Reboots all nodes in the cluster
93
+ def reboot
94
+ nodes.each {|n| n.reboot }
95
+ end
96
+
97
+ # Forces the cluster nodes to restart/reload their JavaScript VMs,
98
+ # effectively reloading any user-provided code.
99
+ def js_reload
100
+ nodes.each {|n| n.js_reload }
101
+ end
102
+
103
+ # Attaches to the console on all nodes, returning a list of
104
+ # {Riak::Node::Console} objects.
105
+ # @return [Array<Riak::Node::Console>] consoles for all running
106
+ # nodes, with nil for nodes that aren't running or otherwise
107
+ # fail to connect
108
+ def attach
109
+ nodes.map do |n|
110
+ begin
111
+ n.attach
112
+ rescue ArgumentError, SystemCallError
113
+ nil
114
+ end
115
+ end
116
+ end
117
+
118
+ # Executes the given block on each node against the node's
119
+ # console. You could use this to send Erlang commands to all nodes
120
+ # in the cluster.
121
+ # @yield [console] A block of commands to be run against the
122
+ # console
123
+ # @yieldparam [Riak::Node::Console] console A console manager for
124
+ # sending commands to the current node in the iteration
125
+ def with_console(&block)
126
+ nodes.each do |n|
127
+ n.with_console(&block)
128
+ end
129
+ end
130
+
131
+ # Is the cluster started?
132
+ def started?
133
+ nodes.all? {|n| n.started? }
134
+ end
135
+
136
+ # Is the cluster stopped?
137
+ def stopped?
138
+ nodes.all? {|n| n.stopped? }
139
+ end
140
+
141
+ # Joins the nodes together into a cluster.
142
+ # @note This method relies on cluster membership changes present
143
+ # in the 1.0 series of Riak, and is NOT safe on 0.14 and
144
+ # earlier.
145
+ def join
146
+ claimant = nodes.first.name # Not really the claimant, just a
147
+ # node to join to
148
+ nodes[1..-1].each {|n| n.join(claimant) unless n.peers.include?(claimant) }
149
+ end
150
+ end
151
+ end
@@ -4,3 +4,4 @@ require 'riak/core_ext/slice'
4
4
  require 'riak/core_ext/stringify_keys'
5
5
  require 'riak/core_ext/symbolize_keys'
6
6
  require 'riak/core_ext/to_param'
7
+ require 'riak/core_ext/deep_dup'
@@ -0,0 +1,13 @@
1
+ unless {}.respond_to?(:deep_dup)
2
+ class Hash
3
+ # Returns a deep copy of hash.
4
+ def deep_dup
5
+ duplicate = self.dup
6
+ duplicate.each_pair do |k,v|
7
+ tv = duplicate[k]
8
+ duplicate[k] = tv.is_a?(Hash) && v.is_a?(Hash) ? tv.deep_dup : v
9
+ end
10
+ duplicate
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ unless Object.new.respond_to?(:to_json)
2
+ # @private
3
+ class Object
4
+ def to_json(*args)
5
+ Riak::JSON.encode(self)
6
+ end
7
+ end
8
+
9
+ # @private
10
+ class Symbol
11
+ def to_json(*args)
12
+ to_s.to_json(*args)
13
+ end
14
+ end
15
+ end
@@ -1,7 +1,7 @@
1
1
  unless {}.respond_to? :stringify_keys
2
2
  class Hash
3
3
  def stringify_keys
4
- inject({}) do |hash, pair|
4
+ inject({}) do |hash, pair|
5
5
  hash[pair[0].to_s] = pair[1]
6
6
  hash
7
7
  end
@@ -1,7 +1,7 @@
1
1
  unless {}.respond_to? :symbolize_keys
2
2
  class Hash
3
3
  def symbolize_keys
4
- inject({}) do |hash, pair|
4
+ inject({}) do |hash, pair|
5
5
  hash[pair[0].to_sym] = pair[1]
6
6
  hash
7
7
  end
@@ -0,0 +1,6 @@
1
+ if defined? Encoding
2
+ Encoding.default_internal = "UTF-8" if Encoding.default_internal.nil? ||
3
+ !Encoding.default_internal.ascii_compatible?
4
+ else
5
+ $KCODE = "U"
6
+ end
@@ -1,16 +1,3 @@
1
- # Copyright 2010 Sean Cribbs, Sonian Inc., and Basho Technologies, Inc.
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
1
 
15
2
  require 'riak/util/translation'
16
3
  require 'riak/json'
@@ -48,7 +35,7 @@ module Riak
48
35
  def is_json?
49
36
  headers['content-type'].include?('application/json')
50
37
  end
51
-
38
+
52
39
  # @return [true,false] whether the error represents a "not found" response
53
40
  def not_found?
54
41
  @code.to_i == 404
@@ -79,7 +66,7 @@ module Riak
79
66
  false
80
67
  end
81
68
  end
82
-
69
+
83
70
  # @return [true,false] whether the error represents a "not found" response
84
71
  def not_found?
85
72
  @not_found
@@ -1,16 +1,3 @@
1
- # Copyright 2010 Sean Cribbs, Sonian Inc., and Basho Technologies, Inc.
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
1
  require 'i18n'
15
2
 
16
3
  I18n.load_path << File.expand_path("../locale/en.yml", __FILE__)