ripple 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,3 +2,4 @@ These developers have contributed code to Ripple:
2
2
 
3
3
  * Sean Cribbs - sean@basho.com
4
4
  * John Lynch - john@rigelgroupllc.com
5
+ * Adam Hunter - adamhunter@me.com
@@ -76,15 +76,15 @@ class Email
76
76
  property :body, String
77
77
  end
78
78
 
79
- email = Email.find("37458abc752f8413e") # GET /raw/emails/37458abc752f8413e
79
+ email = Email.find("37458abc752f8413e") # GET /riak/emails/37458abc752f8413e
80
80
  email.from = "someone@nowhere.net"
81
- email.save # PUT /raw/emails/37458abc752f8413e
81
+ email.save # PUT /riak/emails/37458abc752f8413e
82
82
 
83
83
  reply = Email.new
84
84
  reply.from = "justin@bashoooo.com"
85
85
  reply.to = "sean@geeemail.com"
86
86
  reply.body = "Riak is a good fit for scalable Ruby apps."
87
- reply.save # POST /raw/emails (Riak-assigned key)
87
+ reply.save # POST /riak/emails (Riak-assigned key)
88
88
  </pre></notextile>
89
89
 
90
90
  h2. How to Contribute
@@ -120,7 +120,9 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
120
120
  See the License for the specific language governing permissions and
121
121
  limitations under the License.
122
122
 
123
- h2. Auxillary License
123
+ h2. Auxillary Licenses
124
124
 
125
125
  The included photo (spec/fixtures/cat.jpg) is Copyright &copy;2009 "Sean Cribbs":http://seancribbs.com/, and is
126
126
  licensed under the "Creative Commons Attribution Non-Commercial 3.0":http://creativecommons.org/licenses/by-nc/3.0 license. !http://i.creativecommons.org/l/by-nc/3.0/88x31.png!
127
+
128
+ The "Poor Man's Fibers" implementation (lib/riak/util/fiber1.8.rb) is Copyright &copy;2008 Aman Gupta.
@@ -1,5 +1,25 @@
1
1
  h1. Ripple Release Notes
2
2
 
3
+ h2. 0.6.0 Feature Release - 2010-03-05
4
+
5
+ This release contains enhancements and bugfixes in preparation for the
6
+ Riak 0.9 release.
7
+
8
+ * The CurbBackend now uses fibers to prevent curl-handle corruption when
9
+ a block is given to streaming operations.
10
+ * The default prefix is now "/riak/" to match the latest version of Riak.
11
+ * The client configuration for Ripple is now used.
12
+ * Added Bucket#new and Bucket#get_or_new for easily creating new objects.
13
+ * Added Bucket#allow_mult and Bucket#n_value accessors for more easily setting
14
+ bucket properties.
15
+ * Added timestamps! method for easily adding created_at/updated_at to documents.
16
+ [Adam Hunter]
17
+ * The 'links' collection on RObject is now a Set instead of an Array.
18
+ * All literal messages are now stored in YAML localization files.
19
+ * Object siblings (caused by concurrent updates when allow_mult is true) can now
20
+ be accessed directly.
21
+ * Map-reduce jobs now have timeouts (in parity with Riak).
22
+
3
23
  h2. 0.5.1 Patch Release - 2010-02-22
4
24
 
5
25
  This is a minor release with fixes for Ruby 1.9, bundler/edge Rails,
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.1
1
+ 0.6.0
@@ -34,7 +34,7 @@ module Riak
34
34
  def initialize(client, name)
35
35
  raise ArgumentError, t("client_type", :client => client.inspect) unless Client === client
36
36
  raise ArgumentError, t("string_type", :string => name.inspect) unless String === name
37
- @client, @name = client, name
37
+ @client, @name, @props = client, name, {}
38
38
  end
39
39
 
40
40
  # Load information for the bucket from a response given by the {Riak::Client::HTTPBackend}.
@@ -92,11 +92,61 @@ module Riak
92
92
  # @return [Riak::RObject] the object
93
93
  # @raise [FailedRequest] if the object is not found or some other error occurs
94
94
  def get(key, options={})
95
- response = @client.http.get(200, @client.prefix, name, key, options, {})
95
+ code = allow_mult ? [200,300] : 200
96
+ response = @client.http.get(code, @client.prefix, name, key, options, {})
96
97
  RObject.new(self, key).load(response)
97
98
  end
98
99
  alias :[] :get
99
100
 
101
+ # Create a new blank object
102
+ # @param [String] key the key of the new object
103
+ # @return [RObject] the new, unsaved object
104
+ def new(key=nil)
105
+ RObject.new(self, key).tap do |obj|
106
+ obj.content_type = "application/json"
107
+ end
108
+ end
109
+
110
+ # Fetches an object if it exists, otherwise creates a new one with the given key
111
+ # @param [String] key the key to fetch or create
112
+ # @return [RObject] the new or existing object
113
+ def get_or_new(key, options={})
114
+ begin
115
+ get(key, options)
116
+ rescue Riak::FailedRequest => fr
117
+ if fr.code.to_i == 404
118
+ new(key)
119
+ else
120
+ raise fr
121
+ end
122
+ end
123
+ end
124
+
125
+ # @return [true, false] whether the bucket allows divergent siblings
126
+ def allow_mult
127
+ props['allow_mult']
128
+ end
129
+
130
+ # Set the allow_mult property. *NOTE* This will result in a PUT request to Riak.
131
+ # @param [true, false] value whether the bucket should allow siblings
132
+ def allow_mult=(value)
133
+ self.props = props.merge('allow_mult' => value)
134
+ value
135
+ end
136
+
137
+ # @return [Fixnum] the N value, or number of replicas for this bucket
138
+ def n_value
139
+ props['n_val']
140
+ end
141
+
142
+ # Set the N value (number of replicas). *NOTE* This will result in a PUT request to Riak.
143
+ # Setting this value after the bucket has objects stored in it may have unpredictable results.
144
+ # @param [Fixnum] value the number of replicas the bucket should keep of each object
145
+ def n_value=(value)
146
+ self.props = props.merge('n_val' => value)
147
+ value
148
+ end
149
+
100
150
  # @return [String] a representation suitable for IRB and debugging output
101
151
  def inspect
102
152
  "#<Riak::Bucket #{client.http.path(client.prefix, name).to_s}#{" keys=[#{keys.join(',')}]" if defined?(@keys)}>"
@@ -47,7 +47,7 @@ module Riak
47
47
  # @param [Hash] options configuration options for the client
48
48
  # @option options [String] :host ('127.0.0.1') The host or IP address for the Riak endpoint
49
49
  # @option options [Fixnum] :port (8098) The port of the Riak HTTP endpoint
50
- # @option options [String] :prefix ('/raw/') The URL path prefix to the "raw" HTTP endpoint
50
+ # @option options [String] :prefix ('/riak/') The URL path prefix to the main HTTP endpoint
51
51
  # @option options [String] :mapred ('/mapred') The path to the map-reduce HTTP endpoint
52
52
  # @option options [Fixnum, String] :client_id (rand(MAX_CLIENT_ID)) The internal client ID used by Riak to route responses
53
53
  # @raise [ArgumentError] raised if any options are invalid
@@ -56,7 +56,7 @@ module Riak
56
56
  self.host = options[:host] || "127.0.0.1"
57
57
  self.port = options[:port] || 8098
58
58
  self.client_id = options[:client_id] || make_client_id
59
- self.prefix = options[:prefix] || "/raw/"
59
+ self.prefix = options[:prefix] || "/riak/"
60
60
  self.mapred = options[:mapred] || "/mapred"
61
61
  raise ArgumentError, t("missing_host_and_port") unless @host && @port
62
62
  end
@@ -13,6 +13,12 @@
13
13
  # limitations under the License.
14
14
  require 'riak'
15
15
 
16
+ begin
17
+ require 'fiber'
18
+ rescue LoadError
19
+ require 'riak/util/fiber1.8'
20
+ end
21
+
16
22
  module Riak
17
23
  class Client
18
24
  # An HTTP backend for Riak::Client that uses the 'curb' library/gem.
@@ -20,44 +26,57 @@ module Riak
20
26
  # the backend based on Net::HTTP.
21
27
  # Conforms to the Riak::Client::HTTPBackend interface.
22
28
  class CurbBackend < HTTPBackend
23
- # @private
24
- def initialize(client)
25
- super
26
- @curl = Curl::Easy.new
27
- @curl.follow_location = false
28
- @curl.on_header do |header_line|
29
- @response_headers.parse(header_line)
30
- header_line.size
31
- end
32
- end
33
-
34
29
  private
35
30
  def perform(method, uri, headers, expect, data=nil)
36
31
  # Setup
37
- @curl.headers = headers
38
- @curl.url = uri.to_s
39
- @response_headers = Riak::Util::Headers.new
40
- @curl.on_body {|chunk| yield chunk; chunk.size } if block_given?
41
-
32
+ curl.headers = headers
33
+ curl.url = uri.to_s
34
+ response_headers.initialize_http_header(nil)
35
+ if block_given?
36
+ _curl = curl
37
+ Fiber.new {
38
+ f = Fiber.current
39
+ _curl.on_body {|chunk| f.resume(chunk); chunk.size }
40
+ loop do
41
+ yield Fiber.yield
42
+ end
43
+ }.resume
44
+ else
45
+ curl.on_body
46
+ end
42
47
  # Perform
43
48
  case method
44
49
  when :put, :post
45
- @curl.send("http_#{method}", data)
50
+ curl.send("http_#{method}", data)
46
51
  else
47
- @curl.send("http_#{method}")
52
+ curl.send("http_#{method}")
48
53
  end
49
54
 
50
55
  # Verify
51
- if valid_response?(expect, @curl.response_code)
52
- result = { :headers => @response_headers.to_hash, :code => @curl.response_code.to_i }
53
- if return_body?(method, @curl.response_code, block_given?)
54
- result[:body] = @curl.body_str
56
+ if valid_response?(expect, curl.response_code)
57
+ result = { :headers => response_headers.to_hash, :code => curl.response_code.to_i }
58
+ if return_body?(method, curl.response_code, block_given?)
59
+ result[:body] = curl.body_str
55
60
  end
56
61
  result
57
62
  else
58
- raise FailedRequest.new(method, expect, @curl.response_code, @response_headers.to_hash, @curl.body_str)
63
+ raise FailedRequest.new(method, expect, curl.response_code, response_headers.to_hash, curl.body_str)
59
64
  end
60
65
  end
66
+
67
+ def curl
68
+ Thread.current[:curl_easy_handle] ||= Curl::Easy.new.tap do |c|
69
+ c.follow_location = false
70
+ c.on_header do |header_line|
71
+ response_headers.parse(header_line)
72
+ header_line.size
73
+ end
74
+ end
75
+ end
76
+
77
+ def response_headers
78
+ Thread.current[:response_headers] ||= Riak::Util::Headers.new
79
+ end
61
80
  end
62
81
  end
63
82
  end
@@ -35,14 +35,14 @@ module Riak
35
35
  @url, @rel = url, rel
36
36
  end
37
37
 
38
- # @return [String] bucket_name, if the Link url is a known Riak link ("/raw/<bucket>/<key>")
38
+ # @return [String] bucket_name, if the Link url is a known Riak link ("/riak/<bucket>/<key>")
39
39
  def bucket
40
- $1 if url =~ %r{/raw/([^/]+)/?}
40
+ $1 if url =~ %r{/riak/([^/]+)/?}
41
41
  end
42
42
 
43
- # @return [String] key, if the Link url is a known Riak link ("/raw/<bucket>/<key>")
43
+ # @return [String] key, if the Link url is a known Riak link ("/riak/<bucket>/<key>")
44
44
  def key
45
- $1 if url =~ %r{/raw/[^/]+/([^/]+)/?}
45
+ $1 if url =~ %r{/riak/[^/]+/([^/]+)/?}
46
46
  end
47
47
 
48
48
  def inspect; to_s; end
@@ -124,10 +124,18 @@ module Riak
124
124
  self
125
125
  end
126
126
 
127
+ # Sets the timeout for the map-reduce job.
128
+ # @param [Fixnum] value the job timeout, in milliseconds
129
+ def timeout(value)
130
+ @timeout = value
131
+ end
132
+
127
133
  # Convert the job to JSON for submission over the HTTP interface.
128
134
  # @return [String] the JSON representation
129
135
  def to_json(options={})
130
- ActiveSupport::JSON.encode({"inputs" => inputs, "query" => query.map(&:as_json)}, options)
136
+ hash = {"inputs" => inputs, "query" => query.map(&:as_json)}
137
+ hash['timeout'] = @timeout.to_i if @timeout
138
+ ActiveSupport::JSON.encode(hash, options)
131
139
  end
132
140
 
133
141
  # Executes this map-reduce job.
@@ -12,6 +12,7 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
  require 'riak'
15
+ require 'set'
15
16
 
16
17
  module Riak
17
18
  # Parent class of all object types supported by ripple. {Riak::RObject} represents
@@ -36,7 +37,7 @@ module Riak
36
37
  # @return [Object] the data stored in Riak at this object's key. Varies in format by content-type, defaulting to String from the response body.
37
38
  attr_accessor :data
38
39
 
39
- # @return [Array<Link>] an array of {Riak::Link} objects for relationships between this object and other resources
40
+ # @return [Set<Link>] an Set of {Riak::Link} objects for relationships between this object and other resources
40
41
  attr_accessor :links
41
42
 
42
43
  # @return [String] the ETag header from the most recent HTTP response, useful for caching and reloading
@@ -54,7 +55,8 @@ module Riak
54
55
  # @see Bucket#get
55
56
  def initialize(bucket, key=nil)
56
57
  @bucket, @key = bucket, key
57
- @links, @meta = [], {}
58
+ @links, @meta = Set.new, {}
59
+ yield self if block_given?
58
60
  end
59
61
 
60
62
  # Load object data from an HTTP response
@@ -63,7 +65,7 @@ module Riak
63
65
  extract_header(response, "location", :key) {|v| URI.unescape(v.split("/").last) }
64
66
  extract_header(response, "content-type", :content_type)
65
67
  extract_header(response, "x-riak-vclock", :vclock)
66
- extract_header(response, "link", :links) {|v| Link.parse(v) }
68
+ extract_header(response, "link", :links) {|v| Set.new(Link.parse(v)) }
67
69
  extract_header(response, "etag", :etag)
68
70
  extract_header(response, "last-modified", :last_modified) {|v| Time.httpdate(v) }
69
71
  @meta = response[:headers].inject({}) do |h,(k,v)|
@@ -72,6 +74,8 @@ module Riak
72
74
  end
73
75
  h
74
76
  end
77
+ @conflict = response[:code].try(:to_i) == 300 && content_type =~ /multipart\/mixed/
78
+ @siblings = nil
75
79
  @data = deserialize(response[:body]) if response[:body].present?
76
80
  self
77
81
  end
@@ -126,8 +130,9 @@ module Riak
126
130
  def reload(options={})
127
131
  force = options.delete(:force)
128
132
  return self unless @key && (@vclock || force)
129
- response = @bucket.client.http.get([200, 304], @bucket.client.prefix, @bucket.name, @key, options, reload_headers)
130
- load(response) if response[:code] == 200
133
+ codes = @bucket.allow_mult ? [200,300,304] : [200,304]
134
+ response = @bucket.client.http.get(codes, @bucket.client.prefix, @bucket.name, @key, options, reload_headers)
135
+ load(response) unless response[:code] == 304
131
136
  self
132
137
  end
133
138
 
@@ -141,6 +146,24 @@ module Riak
141
146
  freeze
142
147
  end
143
148
 
149
+ # Returns sibling objects when in conflict.
150
+ # @return [Array<RObject>] an array of conflicting sibling objects for this key
151
+ # @return [self] this object when not in conflict
152
+ def siblings
153
+ return self unless conflict?
154
+ @siblings ||= Multipart.parse(data, Multipart.extract_boundary(content_type)).map do |part|
155
+ RObject.new(self.bucket, self.key) do |sibling|
156
+ sibling.load(part)
157
+ sibling.vclock = vclock
158
+ end
159
+ end
160
+ end
161
+
162
+ # @return [true,false] Whether this object has conflicting sibling objects (divergent vclocks)
163
+ def conflict?
164
+ @conflict.present?
165
+ end
166
+
144
167
  # Serializes the internal object data for sending to Riak. Differs based on the content-type.
145
168
  # This method is called internally when storing the object.
146
169
  # Automatically serialized formats:
@@ -207,12 +230,12 @@ module Riak
207
230
  []
208
231
  end
209
232
  end
210
-
233
+
211
234
  # Converts the object to a link suitable for linking other objects to it
212
235
  def to_link(tag=nil)
213
236
  Link.new(@bucket.client.http.path(@bucket.client.prefix, @bucket.name, @key).path, tag)
214
237
  end
215
-
238
+
216
239
  private
217
240
  def extract_header(response, name, attribute=nil)
218
241
  if response[:headers][name].present?
@@ -0,0 +1,48 @@
1
+ # Poor Man's Fiber (API compatible Thread based Fiber implementation for Ruby 1.8)
2
+ # (c) 2008 Aman Gupta (tmm1)
3
+
4
+ unless defined? Fiber
5
+ require 'thread'
6
+
7
+ class FiberError < StandardError; end
8
+
9
+ class Fiber
10
+ def initialize
11
+ raise ArgumentError, 'new Fiber requires a block' unless block_given?
12
+
13
+ @yield = Queue.new
14
+ @resume = Queue.new
15
+
16
+ @thread = Thread.new{ @yield.push [ *yield(*@resume.pop) ] }
17
+ @thread.abort_on_exception = true
18
+ @thread[:fiber] = self
19
+ end
20
+ attr_reader :thread
21
+
22
+ def resume *args
23
+ raise FiberError, 'dead fiber called' unless @thread.alive?
24
+ @resume.push(args)
25
+ result = @yield.pop
26
+ result.size > 1 ? result : result.first
27
+ end
28
+
29
+ def yield *args
30
+ @yield.push(args)
31
+ result = @resume.pop
32
+ result.size > 1 ? result : result.first
33
+ end
34
+
35
+ def self.yield *args
36
+ raise FiberError, "can't yield from root fiber" unless fiber = Thread.current[:fiber]
37
+ fiber.yield(*args)
38
+ end
39
+
40
+ def self.current
41
+ Thread.current[:fiber] or raise FiberError, 'not inside a fiber'
42
+ end
43
+
44
+ def inspect
45
+ "#<#{self.class}:0x#{self.object_id.to_s(16)}>"
46
+ end
47
+ end
48
+ end
@@ -18,7 +18,7 @@ module Riak
18
18
  # The specification of how to follow links from one object to another in Riak,
19
19
  # when using the link-walker resource.
20
20
  # Example link-walking operation:
21
- # GET /raw/artists/REM/albums,_,_/tracks,_,1
21
+ # GET /riak/artists/REM/albums,_,_/tracks,_,1
22
22
  # This operation would have two WalkSpecs:
23
23
  # Riak::WalkSpec.new({:bucket => 'albums'})
24
24
  # Riak::WalkSpec.new({:bucket => 'tracks', :result => true})
@@ -28,10 +28,12 @@ module Ripple
28
28
  autoload :PropertyTypeMismatch
29
29
  autoload :Translation
30
30
 
31
+ DEFAULT_CONFIG = {}
32
+
31
33
  class << self
32
34
  # @return [Riak::Client] The client for the current thread.
33
35
  def client
34
- Thread.current[:ripple_client] ||= Riak::Client.new
36
+ Thread.current[:ripple_client] ||= Riak::Client.new(config)
35
37
  end
36
38
 
37
39
  # Sets the client for the current thread.