couchbase 1.2.1-x86-mingw32 → 1.2.2-x86-mingw32

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.
data/lib/couchbase.rb CHANGED
@@ -21,6 +21,7 @@ require 'ext/multi_json_fix'
21
21
  require 'yaji'
22
22
  require 'uri'
23
23
  require 'couchbase_ext'
24
+ require 'couchbase/constants'
24
25
  require 'couchbase/utils'
25
26
  require 'couchbase/bucket'
26
27
  require 'couchbase/view_row'
@@ -109,7 +109,7 @@ module Couchbase
109
109
  if obj['doc']
110
110
  obj['doc']['value'] = obj['doc'].delete('json')
111
111
  end
112
- doc = ViewRow.wrap(self, obj)
112
+ doc = DesignDoc.wrap(self, obj)
113
113
  key = doc.id.sub(/^_design\//, '')
114
114
  next if self.environment == :production && key =~ /dev_/
115
115
  docmap[key] = doc
@@ -0,0 +1,12 @@
1
+ module Couchbase
2
+ module Constants # :nodoc:
3
+ S_ID = 'id'.freeze
4
+ S_DOC = 'doc'.freeze
5
+ S_VALUE = 'value'.freeze
6
+ S_META = 'meta'.freeze
7
+ S_FLAGS = 'flags'.freeze
8
+ S_CAS = 'cas'.freeze
9
+ S_KEY = 'key'.freeze
10
+ S_IS_LAST = Object.new.freeze
11
+ end
12
+ end
@@ -17,5 +17,5 @@
17
17
 
18
18
  # Couchbase ruby client
19
19
  module Couchbase
20
- VERSION = "1.2.1"
20
+ VERSION = "1.2.2"
21
21
  end
@@ -47,8 +47,9 @@ module Couchbase
47
47
  str = super
48
48
  if @type || @reason
49
49
  str.sub(/ \(/, ": #{[@type, @reason].compact.join(": ")} (")
50
+ else
51
+ str
50
52
  end
51
- str
52
53
  end
53
54
  end
54
55
  end
@@ -58,6 +59,88 @@ module Couchbase
58
59
  # @see http://www.couchbase.com/docs/couchbase-manual-2.0/couchbase-views.html
59
60
  class View
60
61
  include Enumerable
62
+ include Constants
63
+
64
+ class ArrayWithTotalRows < Array # :nodoc:
65
+ attr_accessor :total_rows
66
+ alias total_entries total_rows
67
+ end
68
+
69
+ class AsyncHelper # :nodoc:
70
+ include Constants
71
+ EMPTY = []
72
+
73
+ def initialize(wrapper_class, bucket, include_docs, quiet, block)
74
+ @wrapper_class = wrapper_class
75
+ @bucket = bucket
76
+ @block = block
77
+ @quiet = quiet
78
+ @include_docs = include_docs
79
+ @queue = []
80
+ @first = @shift = 0
81
+ @completed = false
82
+ end
83
+
84
+ # Register object in the emitter.
85
+ def push(obj)
86
+ if @include_docs
87
+ @queue << obj
88
+ @bucket.get(obj[S_ID], :extended => true, :quiet => @quiet) do |res|
89
+ obj[S_DOC] = {
90
+ S_VALUE => res.value,
91
+ S_META => {
92
+ S_ID => obj[S_ID],
93
+ S_FLAGS => res.flags,
94
+ S_CAS => res.cas
95
+ }
96
+ }
97
+ check_for_ready_documents
98
+ end
99
+ else
100
+ old_obj = @queue.shift
101
+ @queue << obj
102
+ block_call(old_obj) if old_obj
103
+ end
104
+ end
105
+
106
+ def complete!
107
+ if @include_docs
108
+ @completed = true
109
+ check_for_ready_documents
110
+ elsif !@queue.empty?
111
+ obj = @queue.shift
112
+ obj[S_IS_LAST] = true
113
+ block_call obj
114
+ end
115
+ end
116
+
117
+ private
118
+
119
+ def block_call(obj)
120
+ @block.call @wrapper_class.wrap(@bucket, obj)
121
+ end
122
+
123
+ def check_for_ready_documents
124
+ shift = @shift
125
+ queue = @queue
126
+ save_last = @completed ? 0 : 1
127
+ while @first < queue.size + shift - save_last
128
+ obj = queue[@first - shift]
129
+ break unless obj[S_DOC]
130
+ queue[@first - shift] = nil
131
+ @first += 1
132
+ if @completed && @first == queue.size + shift
133
+ obj[S_IS_LAST] = true
134
+ end
135
+ block_call obj
136
+ end
137
+ if @first - shift > queue.size / 2
138
+ queue[0, @first - shift] = EMPTY
139
+ @shift = @first
140
+ end
141
+ end
142
+
143
+ end
61
144
 
62
145
  attr_reader :params
63
146
 
@@ -112,6 +195,16 @@ module Couchbase
112
195
  fetch(params) {|doc| yield(doc)}
113
196
  end
114
197
 
198
+ def first(params = {})
199
+ params = params.merge(:limit => 1)
200
+ fetch(params).first
201
+ end
202
+
203
+ def take(n, params = {})
204
+ params = params.merge(:limit => n)
205
+ fetch(params)
206
+ end
207
+
115
208
  # Registers callback function for handling error objects in view
116
209
  # results stream.
117
210
  #
@@ -259,93 +352,150 @@ module Couchbase
259
352
  # doc.recent_posts_with_comments(:start_key => [post_id, 0],
260
353
  # :end_key => [post_id, 1],
261
354
  # :include_docs => true)
262
- def fetch(params = {})
355
+ def fetch(params = {}, &block)
263
356
  params = @params.merge(params)
357
+ include_docs = params.delete(:include_docs)
358
+ quiet = params.delete(:quiet){ true }
359
+
264
360
  options = {:chunked => true, :extended => true, :type => :view}
265
361
  if body = params.delete(:body)
266
362
  body = MultiJson.dump(body) unless body.is_a?(String)
267
363
  options.update(:body => body, :method => params.delete(:method) || :post)
268
364
  end
269
- include_docs = params.delete(:include_docs)
270
- quiet = true
271
- if params.has_key?(:quiet)
272
- quiet = params.delete(:quiet)
273
- end
274
365
  path = Utils.build_query(@endpoint, params)
275
366
  request = @bucket.make_http_request(path, options)
276
- res = []
367
+
368
+ if @bucket.async?
369
+ if block
370
+ fetch_async(request, include_docs, quiet, block)
371
+ end
372
+ else
373
+ fetch_sync(request, include_docs, quiet, block)
374
+ end
375
+ end
376
+
377
+ # Method for fetching asynchronously all rows and passing array to callback
378
+ #
379
+ # Parameters are same as for {View#fetch} method, but callback is called for whole set for
380
+ # rows instead of one by each.
381
+ #
382
+ # @example
383
+ # con.run do
384
+ # doc.recent_posts.fetch_all do |posts|
385
+ # do_something_with_all_posts(posts)
386
+ # end
387
+ # end
388
+ def fetch_all(params = {}, &block)
389
+ return fetch(params) unless @bucket.async?
390
+ raise ArgumentError, "Block needed for fetch_all in async mode" unless block
391
+
392
+ all = []
393
+ fetch(params) do |row|
394
+ all << row
395
+ if row.last?
396
+ @bucket.create_timer(0) { block.call(all) }
397
+ end
398
+ end
399
+ end
400
+
401
+
402
+ # Returns a string containing a human-readable representation of the {View}
403
+ #
404
+ # @return [String]
405
+ def inspect
406
+ %(#<#{self.class.name}:#{self.object_id} @endpoint=#{@endpoint.inspect} @params=#{@params.inspect}>)
407
+ end
408
+
409
+ private
410
+
411
+ def send_error(*args)
412
+ if @on_error
413
+ @on_error.call(*args.take(2))
414
+ else
415
+ raise Error::View.new(*args)
416
+ end
417
+ end
418
+
419
+ def fetch_async(request, include_docs, quiet, block)
420
+ filter = ["/rows/", "/errors/"]
421
+ parser = YAJI::Parser.new(:filter => filter, :with_path => true)
422
+ helper = AsyncHelper.new(@wrapper_class, @bucket, include_docs, quiet, block)
423
+
277
424
  request.on_body do |chunk|
278
- res << chunk
279
- request.pause if chunk.value.nil? || chunk.error
425
+ if chunk.success?
426
+ parser << chunk.value if chunk.value
427
+ helper.complete! if chunk.completed?
428
+ else
429
+ send_error("http_error", chunk.error)
430
+ end
431
+ end
432
+
433
+ parser.on_object do |path, obj|
434
+ case path
435
+ when "/errors/"
436
+ from, reason = obj["from"], obj["reason"]
437
+ send_error(from, reason)
438
+ else
439
+ helper.push(obj)
440
+ end
280
441
  end
442
+
443
+ request.perform
444
+ nil
445
+ end
446
+
447
+ def fetch_sync(request, include_docs, quiet, block)
448
+ res = []
281
449
  filter = ["/rows/", "/errors/"]
282
- filter << "/total_rows" unless block_given?
450
+ unless block
451
+ filter << "/total_rows"
452
+ docs = ArrayWithTotalRows.new
453
+ end
283
454
  parser = YAJI::Parser.new(:filter => filter, :with_path => true)
284
- docs = []
455
+ last_chunk = nil
456
+
457
+ request.on_body do |chunk|
458
+ last_chunk = chunk
459
+ res << chunk.value if chunk.success?
460
+ end
461
+
285
462
  parser.on_object do |path, obj|
286
463
  case path
287
464
  when "/total_rows"
288
465
  # if total_rows key present, save it and take next object
289
- docs.instance_eval("def total_rows; #{obj}; end")
466
+ docs.total_rows = obj
290
467
  when "/errors/"
291
468
  from, reason = obj["from"], obj["reason"]
292
- if @on_error
293
- @on_error.call(from, reason)
294
- else
295
- raise Error::View.new(from, reason)
296
- end
469
+ send_error(from, reason)
297
470
  else
298
471
  if include_docs
299
- val, flags, cas = @bucket.get(obj['id'], :extended => true, :quiet => quiet)
300
- obj['doc'] = {
301
- 'value' => val,
302
- 'meta' => {
303
- 'id' => obj['id'],
304
- 'flags' => flags,
305
- 'cas' => cas
472
+ val, flags, cas = @bucket.get(obj[S_ID], :extended => true, :quiet => quiet)
473
+ obj[S_DOC] = {
474
+ S_VALUE => val,
475
+ S_META => {
476
+ S_ID => obj[S_ID],
477
+ S_FLAGS => flags,
478
+ S_CAS => cas
306
479
  }
307
480
  }
308
481
  end
309
- if block_given?
310
- yield @wrapper_class.wrap(@bucket, obj)
311
- else
312
- docs << @wrapper_class.wrap(@bucket, obj)
313
- end
482
+ doc = @wrapper_class.wrap(@bucket, obj)
483
+ block ? block.call(doc) : docs << doc
314
484
  end
315
485
  end
316
- # run event loop until the terminating chunk will be found
317
- # last_res variable keeps latest known chunk of the result
318
- last_res = nil
319
- while true
320
- # feed response received chunks to the parser
321
- while r = res.shift
322
- if r.error
323
- if @on_error
324
- @on_error.call("http_error", r.error)
325
- break
326
- else
327
- raise Error::View.new("http_error", r.error, nil)
328
- end
329
- end
330
- last_res = r
331
- parser << r.value
332
- end
333
- if last_res.nil? || !last_res.completed? # shall we run the event loop?
334
- request.continue
335
- else
336
- break
486
+
487
+ request.continue
488
+
489
+ if last_chunk.success?
490
+ while value = res.shift
491
+ parser << value
337
492
  end
493
+ else
494
+ send_error("http_error", last_chunk.error, nil)
338
495
  end
339
- # return nil for call with block
340
- block_given? ? nil : docs
341
- end
342
-
343
496
 
344
- # Returns a string containing a human-readable representation of the {View}
345
- #
346
- # @return [String]
347
- def inspect
348
- %(#<#{self.class.name}:#{self.object_id} @endpoint=#{@endpoint.inspect} @params=#{@params.inspect}>)
497
+ # return nil for call with block
498
+ docs
349
499
  end
350
500
  end
351
501
  end
@@ -20,11 +20,11 @@ module Couchbase
20
20
  #
21
21
  # @since 1.2.0
22
22
  #
23
- # It behaves like Hash, but also defines special methods for each view if
24
- # the documnent considered as Design document.
23
+ # It behaves like Hash for document included into row, and has access methods to row data as well.
25
24
  #
26
25
  # @see http://www.couchbase.com/docs/couchbase-manual-2.0/couchbase-views-datastore.html
27
26
  class ViewRow
27
+ include Constants
28
28
 
29
29
  # Undefine as much methods as we can to free names for views
30
30
  instance_methods.each do |m|
@@ -84,26 +84,11 @@ module Couchbase
84
84
  # @return [Hash]
85
85
  attr_accessor :meta
86
86
 
87
- # The list of views defined or empty array
88
- #
89
- # @since 1.2.0
90
- #
91
- # @return [Array<View>]
92
- attr_accessor :views
93
-
94
- # The list of spatial views defined or empty array
95
- #
96
- # @since 1.2.0
97
- #
98
- # @return [Array<View>]
99
- attr_accessor :spatial
100
-
101
87
  # Initialize the document instance
102
88
  #
103
89
  # @since 1.2.0
104
90
  #
105
- # It takes reference to the bucket, data hash. It will define view
106
- # methods if the data object looks like design document.
91
+ # It takes reference to the bucket, data hash.
107
92
  #
108
93
  # @param [Couchbase::Bucket] bucket the reference to connection
109
94
  # @param [Hash] data the data hash, which was built from JSON document
@@ -111,37 +96,14 @@ module Couchbase
111
96
  def initialize(bucket, data)
112
97
  @bucket = bucket
113
98
  @data = data
114
- @key = data['key']
115
- @value = data['value']
116
- if data['doc']
117
- @meta = data['doc']['meta']
118
- @doc = data['doc']['value']
119
- end
120
- @id = data['id'] || @meta && @meta['id']
121
- @views = []
122
- @spatial = []
123
- if design_doc?
124
- if @doc.has_key?('views')
125
- @doc['views'].each do |name, _|
126
- @views << name
127
- self.instance_eval <<-EOV, __FILE__, __LINE__ + 1
128
- def #{name}(params = {})
129
- View.new(@bucket, "\#{@id}/_view/#{name}", params)
130
- end
131
- EOV
132
- end
133
- end
134
- if @doc.has_key?('spatial')
135
- @doc['spatial'].each do |name, _|
136
- @spatial << name
137
- self.instance_eval <<-EOV, __FILE__, __LINE__ + 1
138
- def #{name}(params = {})
139
- View.new(@bucket, "\#{@id}/_spatial/#{name}", params)
140
- end
141
- EOV
142
- end
143
- end
99
+ @key = data[S_KEY]
100
+ @value = data[S_VALUE]
101
+ if data[S_DOC]
102
+ @meta = data[S_DOC][S_META]
103
+ @doc = data[S_DOC][S_VALUE]
144
104
  end
105
+ @id = data[S_ID] || @meta && @meta[S_ID]
106
+ @last = data.delete(S_IS_LAST) || false
145
107
  end
146
108
 
147
109
  # Wraps data hash into ViewRow instance
@@ -156,7 +118,7 @@ module Couchbase
156
118
  #
157
119
  # @return [ViewRow]
158
120
  def self.wrap(bucket, data)
159
- ViewRow.new(bucket, data)
121
+ self.new(bucket, data)
160
122
  end
161
123
 
162
124
  # Get attribute of the document
@@ -198,33 +160,113 @@ module Couchbase
198
160
  @doc[key] = value
199
161
  end
200
162
 
201
- # Check if the document is design
163
+ # Signals if this row is last in a stream
202
164
  #
203
- # @since 1.2.0
165
+ # @since 1.2.1
204
166
  #
205
- # @return [true, false]
206
- def design_doc?
207
- !!(@doc && @id =~ %r(_design/))
167
+ # @return [true, false] +true+ if this row is last in a stream
168
+ def last?
169
+ @last
170
+ end
171
+
172
+ def inspect
173
+ desc = "#<#{self.class.name}:#{self.object_id}"
174
+ [:@id, :@key, :@value, :@doc, :@meta].each do |iv|
175
+ desc << " #{iv}=#{instance_variable_get(iv).inspect}"
176
+ end
177
+ desc << ">"
178
+ desc
208
179
  end
180
+ end
181
+
182
+ # This class encapsulates information about design docs
183
+ #
184
+ # @since 1.2.1
185
+ #
186
+ # It is subclass of ViewRow, but also gives access to view creation through method_missing
187
+ #
188
+ # @see http://www.couchbase.com/docs/couchbase-manual-2.0/couchbase-views-datastore.html
189
+ class DesignDoc < ViewRow
190
+ # It isn't allowed to change design document ID after
191
+ # initialization
192
+ undef id=
193
+
194
+ # Initialize the design doc instance
195
+ #
196
+ # @since 1.2.1
197
+ #
198
+ # It takes reference to the bucket, data hash. It will define view
199
+ # methods if the data object looks like design document.
200
+ #
201
+ # @param [Couchbase::Bucket] bucket the reference to connection
202
+ # @param [Hash] data the data hash, which was built from JSON document
203
+ # representation
204
+ def initialize(bucket, data)
205
+ super
206
+ @all_views = {}
207
+ @views = @doc.has_key?('views') ? @doc['views'].keys : []
208
+ @spatial = @doc.has_key?('spatial') ? @doc['spatial'].keys : []
209
+ @views.each{|name| @all_views[name] = "#{@id}/_view/#{name}"}
210
+ @spatial.each{|name| @all_views[name] = "#{@id}/_spatial/#{name}"}
211
+ end
212
+
213
+ def method_missing(meth, *args)
214
+ if path = @all_views[meth.to_s]
215
+ View.new(@bucket, path, *args)
216
+ else
217
+ super
218
+ end
219
+ end
220
+
221
+ def respond_to?(meth, *args)
222
+ if @all_views[meth.to_s]
223
+ true
224
+ else
225
+ super
226
+ end
227
+ end
228
+
229
+ def method(meth, *args)
230
+ if path = @all_views[meth.to_s]
231
+ lambda{|*p| View.new(@bucket, path, *p)}
232
+ else
233
+ super
234
+ end
235
+ end
236
+
237
+ # The list of views defined or empty array
238
+ #
239
+ # @since 1.2.1
240
+ #
241
+ # @return [Array<View>]
242
+ attr_accessor :views
243
+
244
+ # The list of spatial views defined or empty array
245
+ #
246
+ # @since 1.2.1
247
+ #
248
+ # @return [Array<View>]
249
+ attr_accessor :spatial
209
250
 
210
251
  # Check if the document has views defines
211
252
  #
212
- # @since 1.2.0
253
+ # @since 1.2.1
213
254
  #
214
- # @see ViewRow#views
255
+ # @see DesignDoc#views
215
256
  #
216
257
  # @return [true, false] +true+ if the document have views
217
258
  def has_views?
218
- !!(design_doc? && !@views.empty?)
259
+ !@views.empty?
219
260
  end
220
261
 
221
262
  def inspect
222
- desc = "#<#{self.class.name}:#{self.object_id} "
223
- desc << [:@id, :@key, :@value, :@doc, :@meta, :@views].map do |iv|
224
- "#{iv}=#{instance_variable_get(iv).inspect}"
225
- end.join(' ')
263
+ desc = "#<#{self.class.name}:#{self.object_id}"
264
+ [:@id, :@views, :@spatial].each do |iv|
265
+ desc << " #{iv}=#{instance_variable_get(iv).inspect}"
266
+ end
226
267
  desc << ">"
227
268
  desc
228
269
  end
270
+
229
271
  end
230
272
  end