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 'beefcake'
15
2
 
16
3
  module Riak
@@ -48,6 +35,7 @@ module Riak
48
35
  optional :last_mod, :uint32, 7
49
36
  optional :last_mod_usecs, :uint32, 8
50
37
  repeated :usermeta, RpbPair, 9
38
+ repeated :indexes, RpbPair, 10
51
39
  end
52
40
 
53
41
  # Primary messages
@@ -75,40 +63,57 @@ module Riak
75
63
 
76
64
  class RpbGetReq
77
65
  include Beefcake::Message
78
- required :bucket, :bytes, 1
79
- required :key, :bytes, 2
80
- optional :r, :uint32, 3
66
+ required :bucket, :bytes, 1
67
+ required :key, :bytes, 2
68
+ optional :r, :uint32, 3
69
+ optional :pr, :uint32, 4
70
+ optional :basic_quorum, :bool, 5
71
+ optional :notfound_ok, :bool, 6
72
+ optional :if_modified, :bytes, 7
73
+ optional :head, :bool, 8
74
+ optional :deletedvclock, :bool, 9
81
75
  end
82
76
 
83
77
  class RpbGetResp
84
78
  include Beefcake::Message
85
- repeated :content, RpbContent, 1
86
- optional :vclock, :bytes, 2
79
+ repeated :content, RpbContent, 1
80
+ optional :vclock, :bytes, 2
81
+ optional :unchanged, :bool, 3
87
82
  end
88
83
 
89
84
  class RpbPutReq
90
85
  include Beefcake::Message
91
- required :bucket, :bytes, 1
92
- required :key, :bytes, 2
93
- optional :vclock, :bytes, 3
94
- required :content, RpbContent, 4
95
- optional :w, :uint32, 5
96
- optional :dw, :uint32, 6
97
- optional :return_body, :bool, 7
86
+ required :bucket, :bytes, 1
87
+ optional :key, :bytes, 2
88
+ optional :vclock, :bytes, 3
89
+ required :content, RpbContent, 4
90
+ optional :w, :uint32, 5
91
+ optional :dw, :uint32, 6
92
+ optional :returnbody, :bool, 7
93
+ optional :pw, :uint32, 8
94
+ optional :if_not_modified, :bool, 9
95
+ optional :if_none_match, :bool, 10
96
+ optional :return_head, :bool, 11
98
97
  end
99
98
 
100
- # Optional since it has the same structure as GetResp
101
- # class RpbPutResp
102
- # include Beefcake::Message
103
- # repeated :content, RpbContent, 1
104
- # optional :vclock, :bytes, 2
105
- # end
99
+ class RpbPutResp
100
+ include Beefcake::Message
101
+ repeated :content, RpbContent, 1
102
+ optional :vclock, :bytes, 2
103
+ optional :key, :bytes, 3
104
+ end
106
105
 
107
106
  class RpbDelReq
108
107
  include Beefcake::Message
109
108
  required :bucket, :bytes, 1
110
109
  required :key, :bytes, 2
111
110
  optional :rw, :uint32, 3
111
+ optional :vclock, :bytes, 4
112
+ optional :r, :uint32, 5
113
+ optional :w, :uint32, 6
114
+ optional :pr, :uint32, 7
115
+ optional :pw, :uint32, 8
116
+ optional :dw, :uint32, 9
112
117
  end
113
118
 
114
119
  class RpbListBucketsResp
@@ -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/robject'
15
2
  require 'riak/link'
16
3
  require 'riak/client/beefcake/messages'
@@ -22,13 +9,14 @@ module Riak
22
9
  ENCODING = "Riak".respond_to?(:encoding)
23
10
 
24
11
  # Returns RpbPutReq
25
- def dump_object(robject)
26
- pbuf = RpbPutReq.new(:bucket => maybe_encode(robject.bucket.name))
27
- pbuf.key = maybe_encode(robject.key ||= generate_key)
28
- pbuf.vclock = maybe_encode Base64.decode64(robject.vclock) if robject.vclock
12
+ def dump_object(robject, options={})
13
+ pbuf = RpbPutReq.new(options.merge(:bucket => maybe_encode(robject.bucket.name)))
14
+ pbuf.key = maybe_encode(robject.key) if robject.key # Put w/o key supported!
15
+ pbuf.vclock = maybe_encode(Base64.decode64(robject.vclock)) if robject.vclock
29
16
  pbuf.content = RpbContent.new(:value => maybe_encode(robject.raw_data),
30
17
  :content_type => maybe_encode(robject.content_type),
31
- :links => robject.links.map {|l| encode_link(l) }.compact)
18
+ :links => robject.links.map {|l| encode_link(l) }.compact,
19
+ :indexes => robject.indexes.map {|k,s| encode_index(k,s) }.flatten)
32
20
 
33
21
  pbuf.content.usermeta = robject.meta.map {|k,v| encode_meta(k,v)} if robject.meta.any?
34
22
  pbuf.content.vtag = maybe_encode(robject.etag) if robject.etag.present?
@@ -40,7 +28,9 @@ module Riak
40
28
 
41
29
  # Returns RObject
42
30
  def load_object(pbuf, robject)
31
+ return robject if pbuf.respond_to?(:unchanged) && pbuf.unchanged # Reloading
43
32
  robject.vclock = Base64.encode64(pbuf.vclock).chomp if pbuf.vclock
33
+ robject.key = maybe_unescape(pbuf.key) if pbuf.respond_to?(:key) && pbuf.key # Put w/o key
44
34
  if pbuf.content.size > 1
45
35
  robject.conflict = true
46
36
  robject.siblings = pbuf.content.map do |c|
@@ -48,6 +38,8 @@ module Riak
48
38
  sibling.vclock = robject.vclock
49
39
  load_content(c, sibling)
50
40
  end
41
+
42
+ return robject.attempt_conflict_resolution
51
43
  else
52
44
  load_content(pbuf.content.first, robject)
53
45
  end
@@ -64,6 +56,10 @@ module Riak
64
56
  robject.content_type = pbuf.content_type if pbuf.content_type.present?
65
57
  robject.links = pbuf.links.map(&method(:decode_link)) if pbuf.links.present?
66
58
  pbuf.usermeta.each {|pair| decode_meta(pair, robject.meta) } if pbuf.usermeta.present?
59
+ if pbuf.indexes.present?
60
+ robject.indexes.clear
61
+ pbuf.indexes.each {|pair| decode_index(pair, robject.indexes) }
62
+ end
67
63
  if pbuf.last_mod.present?
68
64
  robject.last_modified = Time.at(pbuf.last_mod)
69
65
  robject.last_modified += pbuf.last_mod_usecs / 1000000 if pbuf.last_mod_usecs.present?
@@ -92,8 +88,20 @@ module Riak
92
88
  :value => maybe_encode(value.to_s))
93
89
  end
94
90
 
91
+ def decode_index(pbuf, hash)
92
+ value = pbuf.key =~ /int$/ ? pbuf.value.to_i : pbuf.value
93
+ hash[pbuf.key] << value
94
+ end
95
+
96
+ def encode_index(key, set)
97
+ set.map do |v|
98
+ RpbPair.new(:key => maybe_encode(key),
99
+ :value => maybe_encode(v.to_s))
100
+ end
101
+ end
102
+
95
103
  def maybe_encode(string)
96
- ENCODING ? string.encode('BINARY') : string
104
+ ENCODING ? string.dup.force_encoding('BINARY') : string
97
105
  end
98
106
  end
99
107
 
@@ -1,17 +1,4 @@
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 'base64'
15
2
  require 'riak/json'
16
3
  require 'riak/client'
17
4
  require 'riak/failed_request'
@@ -44,45 +31,49 @@ module Riak
44
31
  decode_response
45
32
  end
46
33
 
47
- def fetch_object(bucket, key, r=nil)
34
+ def fetch_object(bucket, key, options={})
35
+ options = normalize_quorums(options)
48
36
  bucket = Bucket === bucket ? bucket.name : bucket
49
- req = RpbGetReq.new(:bucket => bucket, :key => key)
50
- req.r = normalize_quorum_value(r) if r
37
+ req = RpbGetReq.new(options.merge(:bucket => maybe_encode(bucket), :key => maybe_encode(key)))
51
38
  write_protobuff(:GetReq, req)
52
39
  decode_response(RObject.new(client.bucket(bucket), key))
53
40
  end
54
41
 
55
- def reload_object(robject, r=nil)
56
- req = RpbGetReq.new(:bucket => robject.bucket.name, :key => robject.key)
57
- req.r = normalize_quorum_value(r) if r
42
+ def reload_object(robject, options={})
43
+ options = normalize_quorums(options)
44
+ options[:bucket] = maybe_encode(robject.bucket.name)
45
+ options[:key] = maybe_encode(robject.key)
46
+ options[:if_modified] = maybe_encode Base64.decode64(robject.vclock) if robject.vclock
47
+ req = RpbGetReq.new(options)
58
48
  write_protobuff(:GetReq, req)
59
49
  decode_response(robject)
60
50
  end
61
51
 
62
- def store_object(robject, returnbody=false, w=nil, dw=nil)
52
+ def store_object(robject, options={})
63
53
  if robject.prevent_stale_writes
64
54
  other = fetch_object(robject.bucket, robject.key)
65
55
  raise Riak::ProtobuffsFailedRequest(:stale_object, t("stale_write_prevented")) unless other.vclock == robject.vclock
66
56
  end
67
- req = dump_object(robject)
68
- req.w = normalize_quorum_value(w) if w
69
- req.dw = normalize_quorum_value(dw) if dw
70
- req.return_body = returnbody
57
+ options = normalize_quorums(options)
58
+ req = dump_object(robject, options)
71
59
  write_protobuff(:PutReq, req)
72
60
  decode_response(robject)
73
61
  end
74
62
 
75
- def delete_object(bucket, key, rw=nil)
63
+ def delete_object(bucket, key, options={})
76
64
  bucket = Bucket === bucket ? bucket.name : bucket
77
- req = RpbDelReq.new(:bucket => bucket, :key => key)
78
- req.rw = normalize_quorum_value(rw) if rw
65
+ options = normalize_quorums(options)
66
+ options[:bucket] = maybe_encode(bucket)
67
+ options[:key] = maybe_encode(key)
68
+ options[:vclock] = Base64.decode64(options[:vclock]) if options[:vclock]
69
+ req = RpbDelReq.new(options)
79
70
  write_protobuff(:DelReq, req)
80
71
  decode_response
81
72
  end
82
73
 
83
74
  def get_bucket_props(bucket)
84
75
  bucket = bucket.name if Bucket === bucket
85
- req = RpbGetBucketReq.new(:bucket => bucket)
76
+ req = RpbGetBucketReq.new(:bucket => maybe_encode(bucket))
86
77
  write_protobuff(:GetBucketReq, req)
87
78
  decode_response
88
79
  end
@@ -90,14 +81,14 @@ module Riak
90
81
  def set_bucket_props(bucket, props)
91
82
  bucket = bucket.name if Bucket === bucket
92
83
  props = props.slice('n_val', 'allow_mult')
93
- req = RpbSetBucketReq.new(:bucket => bucket, :props => RpbBucketProps.new(props))
84
+ req = RpbSetBucketReq.new(:bucket => maybe_encode(bucket), :props => RpbBucketProps.new(props))
94
85
  write_protobuff(:SetBucketReq, req)
95
86
  decode_response
96
87
  end
97
88
 
98
89
  def list_keys(bucket, &block)
99
90
  bucket = bucket.name if Bucket === bucket
100
- req = RpbListKeysReq.new(:bucket => bucket)
91
+ req = RpbListKeysReq.new(:bucket => maybe_encode(bucket))
101
92
  write_protobuff(:ListKeysReq, req)
102
93
  keys = []
103
94
  pump = Pump.new(block) if block_given?
@@ -165,9 +156,12 @@ module Riak
165
156
  when :GetServerInfoResp
166
157
  res = RpbGetServerInfoResp.decode(message)
167
158
  {:node => res.node, :server_version => res.server_version}
168
- when :GetResp, :PutResp
159
+ when :GetResp
169
160
  res = RpbGetResp.decode(message)
170
161
  load_object(res, args.first)
162
+ when :PutResp
163
+ res = RpbPutResp.decode(message)
164
+ load_object(res, args.first)
171
165
  when :ListBucketsResp
172
166
  res = RpbListBucketsResp.decode(message)
173
167
  res.buckets
@@ -180,7 +174,7 @@ module Riak
180
174
  RpbMapRedResp.decode(message)
181
175
  end
182
176
  end
183
- rescue SocketError => e
177
+ rescue SystemCallError, SocketError => e
184
178
  reset_socket
185
179
  raise Riak::ProtobuffsFailedRequest.new(:server_error, e.message)
186
180
  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/failed_request'
16
3
  require 'riak/client/http_backend'
@@ -1,17 +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
-
15
1
  require 'riak/util/escape'
16
2
  require 'riak/util/translation'
17
3
  require 'riak/util/multipart'
@@ -35,7 +21,7 @@ module Riak
35
21
  class HTTPBackend
36
22
  include Util::Escape
37
23
  include Util::Translation
38
-
24
+
39
25
  include TransportMethods
40
26
  include ObjectMethods
41
27
  include Configuration
@@ -53,7 +39,7 @@ module Riak
53
39
  # Pings the server
54
40
  # @return [true,false] whether the server is available
55
41
  def ping
56
- get(200, riak_kv_wm_ping, {}, {})
42
+ get(200, ping_path)
57
43
  true
58
44
  rescue
59
45
  false
@@ -63,20 +49,22 @@ module Riak
63
49
  # @param [Bucket, String] bucket the bucket where the object is
64
50
  # stored
65
51
  # @param [String] key the key of the object
66
- # @param [Fixnum, String, Symbol] r the read quorum for the
67
- # request - how many nodes should concur on the read
52
+ # @param [Hash] options request quorums
53
+ # @option options [Fixnum, String, Symbol] :r the read quorum for the
54
+ # request - how many nodes should concur on the read
55
+ # @option options [Fixnum, String, Symbol] :pr the "primary"
56
+ # read quorum for the request - how many primary partitions
57
+ # must be available
68
58
  # @return [RObject] the fetched object
69
- def fetch_object(bucket, key, r=nil)
59
+ def fetch_object(bucket, key, options={})
70
60
  bucket = Bucket.new(client, bucket) if String === bucket
71
- options = r ? {:r => r} : {}
72
- response = get([200,300],riak_kv_wm_raw, escape(bucket.name), escape(key), options, {})
61
+ response = get([200,300], object_path(bucket.name, key, options))
73
62
  load_object(RObject.new(bucket, key), response)
74
63
  end
75
64
 
76
65
  # Reloads the data for a given RObject, a special case of {#fetch_object}.
77
- def reload_object(robject, r = nil)
78
- options = r ? {:r => r} : {}
79
- response = get([200,300,304], riak_kv_wm_raw, escape(robject.bucket.name), escape(robject.key), options, reload_headers(robject))
66
+ def reload_object(robject, options={})
67
+ response = get([200,300,304], object_path(robject.bucket.name, robject.key, options), reload_headers(robject))
80
68
  if response[:code].to_i == 304
81
69
  robject
82
70
  else
@@ -86,35 +74,37 @@ module Riak
86
74
 
87
75
  # Stores an object
88
76
  # @param [RObject] robject the object to store
89
- # @param [true,false] returnbody (false) whether to update the object
77
+ # @param [Hash] options quorum and storage options
78
+ # @option options [true,false] :returnbody (false) whether to update the object
90
79
  # after write with the new value
91
- # @param [Fixnum, String, Symbol] w the write quorum
92
- # @param [Fixnum, String, Symbol] dw the durable write quorum
93
- def store_object(robject, returnbody=false, w=nil, dw=nil)
94
- query = {}.tap do |q|
95
- q[:returnbody] = returnbody unless returnbody.nil?
96
- q[:w] = w unless w.nil?
97
- q[:dw] = dw unless dw.nil?
98
- end
99
- method, codes, path = if robject.key.present?
100
- [:put, [200,204,300], "#{escape(robject.bucket.name)}/#{escape(robject.key)}"]
101
- else
102
- [:post, 201, escape(robject.bucket.name)]
103
- end
104
- response = send(method, codes, riak_kv_wm_raw, path, query, robject.raw_data, store_headers(robject))
105
- load_object(robject, response) if returnbody
80
+ # @option options [Fixnum, String, Symbol] :w the write quorum
81
+ # @option options [Fixnum, String, Symbol] :pw the "primary"
82
+ # write quorum - how many primary partitions must be available
83
+ # @option options [Fixnum, String, Symbol] :dw the durable write quorum
84
+ def store_object(robject, options={})
85
+ method, codes = if robject.key.present?
86
+ [:put, [200,204,300]]
87
+ else
88
+ [:post, 201]
89
+ end
90
+ response = send(method, codes, object_path(robject.bucket.name, robject.key, options), robject.raw_data, store_headers(robject))
91
+ load_object(robject, response) if options[:returnbody]
106
92
  end
107
93
 
108
94
  # Deletes an object
109
95
  # @param [Bucket, String] bucket the bucket where the object
110
- # lives
96
+ # lives
111
97
  # @param [String] key the key where the object lives
112
- # @param [Fixnum, String, Symbol] rw the read/write quorum for
113
- # the request
114
- def delete_object(bucket, key, rw=nil)
98
+ # @param [Hash] options quorum and delete options
99
+ # @options options [Fixnum, String, Symbol] :rw the read/write quorum for
100
+ # the request
101
+ # @options options [String] :vclock the vector clock of the
102
+ # object to be deleted
103
+ def delete_object(bucket, key, options={})
115
104
  bucket = bucket.name if Bucket === bucket
116
- options = rw ? {:rw => rw} : {}
117
- delete([204, 404], riak_kv_wm_raw, escape(bucket), escape(key), options, {})
105
+ vclock = options.delete(:vclock)
106
+ headers = vclock ? {"X-Riak-VClock" => vclock} : {}
107
+ delete([204, 404], object_path(bucket, key, options), headers)
118
108
  end
119
109
 
120
110
  # Fetches bucket properties
@@ -122,7 +112,7 @@ module Riak
122
112
  # @return [Hash] bucket properties
123
113
  def get_bucket_props(bucket)
124
114
  bucket = bucket.name if Bucket === bucket
125
- response = get(200, riak_kv_wm_raw, escape(bucket), {:keys => false, :props => true}, {})
115
+ response = get(200, bucket_properties_path(bucket))
126
116
  JSON.parse(response[:body])['props']
127
117
  end
128
118
 
@@ -132,7 +122,7 @@ module Riak
132
122
  def set_bucket_props(bucket, props)
133
123
  bucket = bucket.name if Bucket === bucket
134
124
  body = {'props' => props}.to_json
135
- put(204, riak_kv_wm_raw, escape(bucket), body, {"Content-Type" => "application/json"})
125
+ put(204, bucket_properties_path(bucket), body, {"Content-Type" => "application/json"})
136
126
  end
137
127
 
138
128
  # List keys in a bucket
@@ -143,10 +133,10 @@ module Riak
143
133
  # @return [Array<String>] the list of keys, if no block was given
144
134
  def list_keys(bucket, &block)
145
135
  bucket = bucket.name if Bucket === bucket
146
- if block_given?
147
- get(200, riak_kv_wm_raw, escape(bucket), {:props => false, :keys => 'stream'}, {}, &KeyStreamer.new(block))
136
+ if block_given?
137
+ get(200, key_list_path(bucket, :keys => 'stream'), {}, &KeyStreamer.new(block))
148
138
  else
149
- response = get(200, riak_kv_wm_raw, escape(bucket), {:props => false, :keys => true}, {})
139
+ response = get(200, key_list_path(bucket))
150
140
  obj = JSON.parse(response[:body])
151
141
  obj && obj['keys'].map {|k| unescape(k) }
152
142
  end
@@ -155,7 +145,7 @@ module Riak
155
145
  # Lists known buckets
156
146
  # @return [Array<String>] the list of buckets
157
147
  def list_buckets
158
- response = get(200, riak_kv_wm_raw, {:buckets => true}, {})
148
+ response = get(200, bucket_list_path)
159
149
  JSON.parse(response[:body])['buckets']
160
150
  end
161
151
 
@@ -171,10 +161,10 @@ module Riak
171
161
  result = JSON.parse(response[:body])
172
162
  yield result['phase'], result['data']
173
163
  end
174
- post(200, riak_kv_wm_mapred, {:chunked => true}, mr.to_json, {"Content-Type" => "application/json", "Accept" => "application/json"}, &parser)
164
+ post(200, mapred_path({:chunked => true}), mr.to_json, {"Content-Type" => "application/json", "Accept" => "application/json"}, &parser)
175
165
  nil
176
166
  else
177
- response = post(200, riak_kv_wm_mapred, mr.to_json, {"Content-Type" => "application/json", "Accept" => "application/json"})
167
+ response = post(200, mapred_path, mr.to_json, {"Content-Type" => "application/json", "Accept" => "application/json"})
178
168
  begin
179
169
  JSON.parse(response[:body])
180
170
  rescue
@@ -186,7 +176,7 @@ module Riak
186
176
  # Gets health statistics
187
177
  # @return [Hash] information about the server, including stats
188
178
  def stats
189
- response = get(200, riak_kv_wm_stats, {}, {})
179
+ response = get(200, stats_path)
190
180
  JSON.parse(response[:body])
191
181
  end
192
182
 
@@ -197,20 +187,65 @@ module Riak
197
187
  # @return [Array<Array<RObject>>] a list of the matched objects,
198
188
  # grouped by phase
199
189
  def link_walk(robject, walk_specs)
200
- response = get(200, riak_kv_wm_link_walker, escape(robject.bucket.name), escape(robject.key), walk_specs.join("/"))
190
+ response = get(200, link_walk_path(robject.bucket.name, robject.key, walk_specs))
201
191
  if boundary = Util::Multipart.extract_boundary(response[:headers]['content-type'].first)
202
192
  Util::Multipart.parse(response[:body], boundary).map do |group|
203
193
  group.map do |obj|
204
- if obj[:headers] && obj[:body] && obj[:headers]['location']
205
- bucket = $1 if obj[:headers]['location'].first =~ %r{/.*/(.*)/.*$}
206
- load_object(RObject.new(client.bucket(bucket), nil), obj)
194
+ if obj[:headers] && !obj[:headers]['x-riak-deleted'] && !obj[:body].blank? && obj[:headers]['location']
195
+ link = Riak::Link.new(obj[:headers]['location'].first, "")
196
+ load_object(RObject.new(client.bucket(link.bucket), link.key), obj)
207
197
  end
208
- end
198
+ end.compact
209
199
  end
210
200
  else
211
201
  []
212
202
  end
213
203
  end
204
+
205
+ # Performs a secondary-index query.
206
+ # @param [String, Bucket] bucket the bucket to query
207
+ # @param [String] index the index to query
208
+ # @param [String, Integer, Range] query the equality query or
209
+ # range query to perform
210
+ # @return [Array<String>] a list of keys matching the query
211
+ def get_index(bucket, index, query)
212
+ bucket = bucket.name if Bucket === bucket
213
+ path = case query
214
+ when Range
215
+ raise ArgumentError, t('invalid_index_query', :value => query.inspect) unless String === query.begin || Integer === query.end
216
+ index_range_path(bucket, index, query.begin, query.end)
217
+ when String, Integer
218
+ index_eq_path(bucket, index, query)
219
+ else
220
+ raise ArgumentError, t('invalid_index_query', :value => query.inspect)
221
+ end
222
+ response = get(200, path)
223
+ JSON.parse(response[:body])['keys']
224
+ end
225
+
226
+ # (Riak Search) Performs a search query
227
+ # @param [String,nil] index the index to query, or nil for the
228
+ # default
229
+ # @param [String] query the Lucene query to perform
230
+ # @param [Hash] options query options
231
+ # @see Client#search
232
+ def search(index, query, options={})
233
+ response = get(200, solr_select_path(index, query, options.stringify_keys))
234
+ if response[:headers]['content-type'].include?("application/json")
235
+ JSON.parse(response[:body])
236
+ else
237
+ response[:body]
238
+ end
239
+ end
240
+
241
+ # (Riak Search) Updates a search index (includes deletes).
242
+ # @param [String, nil] index the index to update, or nil for the
243
+ # default index.
244
+ # @param [String] updates an XML update string in Solr's required format
245
+ # @see Client#index
246
+ def update_search_index(index, updates)
247
+ post(200, solr_update_path(index), updates, {'Content-Type' => 'text/xml'})
248
+ end
214
249
  end
215
250
  end
216
251
  end