couchbase-jruby-client 0.1.0-java

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 (65) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.ruby-version +1 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +203 -0
  6. data/README.md +347 -0
  7. data/Rakefile +10 -0
  8. data/couchbase-jruby-client.gemspec +30 -0
  9. data/lib/couchbase/async/callback.rb +19 -0
  10. data/lib/couchbase/async/queue.rb +26 -0
  11. data/lib/couchbase/async.rb +139 -0
  12. data/lib/couchbase/bucket.rb +663 -0
  13. data/lib/couchbase/cluster.rb +105 -0
  14. data/lib/couchbase/constants.rb +12 -0
  15. data/lib/couchbase/error.rb +28 -0
  16. data/lib/couchbase/jruby/couchbase_client.rb +22 -0
  17. data/lib/couchbase/jruby/future.rb +8 -0
  18. data/lib/couchbase/operations/arithmetic.rb +301 -0
  19. data/lib/couchbase/operations/delete.rb +104 -0
  20. data/lib/couchbase/operations/get.rb +298 -0
  21. data/lib/couchbase/operations/stats.rb +16 -0
  22. data/lib/couchbase/operations/store.rb +468 -0
  23. data/lib/couchbase/operations/touch.rb +123 -0
  24. data/lib/couchbase/operations/utils.rb +49 -0
  25. data/lib/couchbase/operations.rb +23 -0
  26. data/lib/couchbase/result.rb +43 -0
  27. data/lib/couchbase/transcoder.rb +83 -0
  28. data/lib/couchbase/utils.rb +62 -0
  29. data/lib/couchbase/version.rb +3 -0
  30. data/lib/couchbase/view.rb +506 -0
  31. data/lib/couchbase/view_row.rb +272 -0
  32. data/lib/couchbase.rb +177 -0
  33. data/lib/jars/commons-codec-1.5.jar +0 -0
  34. data/lib/jars/couchbase-client-1.2.0-javadoc.jar +0 -0
  35. data/lib/jars/couchbase-client-1.2.0-sources.jar +0 -0
  36. data/lib/jars/couchbase-client-1.2.0.jar +0 -0
  37. data/lib/jars/httpcore-4.1.1.jar +0 -0
  38. data/lib/jars/httpcore-nio-4.1.1.jar +0 -0
  39. data/lib/jars/jettison-1.1.jar +0 -0
  40. data/lib/jars/netty-3.5.5.Final.jar +0 -0
  41. data/lib/jars/spymemcached-2.10.0-javadoc.jar +0 -0
  42. data/lib/jars/spymemcached-2.10.0-sources.jar +0 -0
  43. data/lib/jars/spymemcached-2.10.0.jar +0 -0
  44. data/test/profile/.gitignore +1 -0
  45. data/test/profile/Gemfile +6 -0
  46. data/test/profile/benchmark.rb +195 -0
  47. data/test/setup.rb +201 -0
  48. data/test/test_arithmetic.rb +177 -0
  49. data/test/test_async.rb +324 -0
  50. data/test/test_bucket.rb +213 -0
  51. data/test/test_cas.rb +78 -0
  52. data/test/test_couchbase.rb +29 -0
  53. data/test/test_couchbase_rails_cache_store.rb +341 -0
  54. data/test/test_delete.rb +125 -0
  55. data/test/test_errors.rb +82 -0
  56. data/test/test_format.rb +161 -0
  57. data/test/test_get.rb +417 -0
  58. data/test/test_stats.rb +57 -0
  59. data/test/test_store.rb +216 -0
  60. data/test/test_timer.rb +42 -0
  61. data/test/test_touch.rb +97 -0
  62. data/test/test_unlock.rb +119 -0
  63. data/test/test_utils.rb +58 -0
  64. data/test/test_version.rb +52 -0
  65. metadata +226 -0
@@ -0,0 +1,43 @@
1
+ module Couchbase
2
+ class Result
3
+
4
+ attr_accessor :error
5
+
6
+ def initialize(attrs = {})
7
+ @bucket = attrs[:bucket]
8
+ @key = attrs[:key]
9
+ @operation = attrs[:op]
10
+ @future = attrs[:future]
11
+ end
12
+
13
+ def operation
14
+ @operation
15
+ end
16
+
17
+ def success?
18
+ @future.get
19
+ end
20
+
21
+ def error
22
+ @error
23
+ end
24
+
25
+ def key
26
+ @key || @future.getKey
27
+ end
28
+
29
+ def value
30
+ @bucket.load @future.get
31
+ rescue MultiJson::LoadError
32
+ nil
33
+ end
34
+
35
+ def cas
36
+ @future.getCas if @future.respond_to?(:getCas)
37
+ end
38
+
39
+ def node
40
+
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,83 @@
1
+ # Author:: Couchbase <info@couchbase.com>
2
+ # Copyright:: 2013 Couchbase, Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require 'multi_json'
19
+
20
+ module Couchbase
21
+
22
+ module Transcoder
23
+
24
+ # module Compat
25
+ # def self.enable!
26
+ # @disabled = false
27
+ # end
28
+
29
+ # def self.disable!
30
+ # @disabled = true
31
+ # end
32
+
33
+ # def self.enabled?
34
+ # !@disabled
35
+ # end
36
+
37
+ # def self.guess_and_load(blob, flags, options = {})
38
+ # case flags & Bucket::FMT_MASK
39
+ # when Bucket::FMT_DOCUMENT
40
+ # MultiJson.load(blob)
41
+ # when Bucket::FMT_MARSHAL
42
+ # ::Marshal.load(blob)
43
+ # when Bucket::FMT_PLAIN
44
+ # blob
45
+ # else
46
+ # raise ArgumentError, "unexpected flags (0x%02x)" % flags
47
+ # end
48
+ # end
49
+ # end
50
+
51
+ module Document
52
+ def self.dump(obj)
53
+ MultiJson.dump(obj)
54
+ end
55
+
56
+ def self.load(blob)
57
+ MultiJson.load(blob)
58
+ end
59
+ end
60
+
61
+ module Marshal
62
+ def self.dump(obj)
63
+ ::Marshal.dump(obj)
64
+ end
65
+
66
+ def self.load(blob)
67
+ ::Marshal.load(blob)
68
+ end
69
+ end
70
+
71
+ module Plain
72
+ def self.dump(obj)
73
+ obj
74
+ end
75
+
76
+ def self.load(blob)
77
+ blob
78
+ end
79
+ end
80
+
81
+ end
82
+
83
+ end
@@ -0,0 +1,62 @@
1
+ # Author:: Couchbase <info@couchbase.com>
2
+ # Copyright:: 2011-2012 Couchbase, Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ module Couchbase
19
+
20
+ class Utils
21
+
22
+ def self.encode_params(params)
23
+ params.map do |k, v|
24
+ next if !v && k.to_s == "group"
25
+ if %w{key keys startkey endkey start_key end_key}.include?(k.to_s)
26
+ v = MultiJson.dump(v)
27
+ end
28
+ if v.class == Array
29
+ build_query(v.map { |x| [k, x] })
30
+ else
31
+ "#{escape(k)}=#{escape(v)}"
32
+ end
33
+ end.compact.join("&")
34
+ end
35
+
36
+ def self.build_query(uri, params = nil)
37
+ uri = uri.dup
38
+ return uri if params.nil? || params.empty?
39
+ uri << "?" << encode_params(params)
40
+ end
41
+
42
+ def self.escape(s)
43
+ s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/nu) {
44
+ '%'+$1.unpack('H2'*bytesize($1)).join('%').upcase
45
+ }.tr(' ', '+')
46
+ end
47
+
48
+ # Return the bytesize of String; uses String#size under Ruby 1.8 and
49
+ # String#bytesize under 1.9.
50
+ if ''.respond_to?(:bytesize)
51
+ def self.bytesize(string)
52
+ string.bytesize
53
+ end
54
+ else
55
+ def self.bytesize(string)
56
+ string.size
57
+ end
58
+ end
59
+
60
+ end
61
+
62
+ end
@@ -0,0 +1,3 @@
1
+ module Couchbase
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,506 @@
1
+ # Author:: Couchbase <info@couchbase.com>
2
+ # Copyright:: 2011 Couchbase, Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require 'base64'
19
+
20
+ module Couchbase
21
+
22
+ module Error
23
+ class View < Base
24
+ attr_reader :from, :reason
25
+
26
+ def initialize(from, reason, prefix = "SERVER: ")
27
+ @from = from
28
+ @reason = reason
29
+ super("#{prefix}#{from}: #{reason}")
30
+ end
31
+ end
32
+
33
+ class HTTP < Base
34
+ attr_reader :type, :reason
35
+
36
+ def parse_body!
37
+ if @body
38
+ hash = MultiJson.load(@body)
39
+ if hash["errors"]
40
+ @type = :invalid_arguments
41
+ @reason = hash["errors"].values.join(" ")
42
+ else
43
+ @type = hash["error"]
44
+ @reason = hash["reason"]
45
+ end
46
+ end
47
+ rescue MultiJson::DecodeError
48
+ @type = @reason = nil
49
+ end
50
+
51
+ def to_s
52
+ str = super
53
+ if @type || @reason
54
+ str.sub(/ \(/, ": #{[@type, @reason].compact.join(": ")} (")
55
+ else
56
+ str
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ # This class implements Couchbase View execution
63
+ #
64
+ # @see http://www.couchbase.com/docs/couchbase-manual-2.0/couchbase-views.html
65
+ class View
66
+ include Enumerable
67
+ include Constants
68
+
69
+ class ArrayWithTotalRows < Array # :nodoc:
70
+ attr_accessor :total_rows
71
+ alias total_entries total_rows
72
+ end
73
+
74
+ class AsyncHelper # :nodoc:
75
+ include Constants
76
+ EMPTY = []
77
+
78
+ def initialize(wrapper_class, bucket, include_docs, quiet, block)
79
+ @wrapper_class = wrapper_class
80
+ @bucket = bucket
81
+ @block = block
82
+ @quiet = quiet
83
+ @include_docs = include_docs
84
+ @queue = []
85
+ @first = @shift = 0
86
+ @completed = false
87
+ end
88
+
89
+ # Register object in the emitter.
90
+ def push(obj)
91
+ if @include_docs
92
+ @queue << obj
93
+ @bucket.get(obj[S_ID], :extended => true, :quiet => @quiet) do |res|
94
+ obj[S_DOC] = {
95
+ S_VALUE => res.value,
96
+ S_META => {
97
+ S_ID => obj[S_ID],
98
+ S_FLAGS => res.flags,
99
+ S_CAS => res.cas
100
+ }
101
+ }
102
+ check_for_ready_documents
103
+ end
104
+ else
105
+ old_obj = @queue.shift
106
+ @queue << obj
107
+ block_call(old_obj) if old_obj
108
+ end
109
+ end
110
+
111
+ def complete!
112
+ if @include_docs
113
+ @completed = true
114
+ check_for_ready_documents
115
+ elsif !@queue.empty?
116
+ obj = @queue.shift
117
+ obj[S_IS_LAST] = true
118
+ block_call obj
119
+ end
120
+ end
121
+
122
+ private
123
+
124
+ def block_call(obj)
125
+ @block.call @wrapper_class.wrap(@bucket, obj)
126
+ end
127
+
128
+ def check_for_ready_documents
129
+ shift = @shift
130
+ queue = @queue
131
+ save_last = @completed ? 0 : 1
132
+ while @first < queue.size + shift - save_last
133
+ obj = queue[@first - shift]
134
+ break unless obj[S_DOC]
135
+ queue[@first - shift] = nil
136
+ @first += 1
137
+ if @completed && @first == queue.size + shift
138
+ obj[S_IS_LAST] = true
139
+ end
140
+ block_call obj
141
+ end
142
+ if @first - shift > queue.size / 2
143
+ queue[0, @first - shift] = EMPTY
144
+ @shift = @first
145
+ end
146
+ end
147
+
148
+ end
149
+
150
+ attr_reader :params
151
+
152
+ # Set up view endpoint and optional params
153
+ #
154
+ # @param [Couchbase::Bucket] bucket Connection object which
155
+ # stores all info about how to make requests to Couchbase views.
156
+ #
157
+ # @param [String] endpoint Full Couchbase View URI.
158
+ #
159
+ # @param [Hash] params Optional parameter which will be passed to
160
+ # {View#fetch}
161
+ #
162
+ def initialize(bucket, endpoint, params = {})
163
+ @bucket = bucket
164
+ @endpoint = endpoint
165
+ @params = {:connection_timeout => 75_000}.merge(params)
166
+ @wrapper_class = params.delete(:wrapper_class) || ViewRow
167
+ unless @wrapper_class.respond_to?(:wrap)
168
+ raise ArgumentError, "wrapper class should reposond to :wrap, check the options"
169
+ end
170
+ end
171
+
172
+ # Yields each document that was fetched by view. It doesn't instantiate
173
+ # all the results because of streaming JSON parser. Returns Enumerator
174
+ # unless block given.
175
+ #
176
+ # @param [Hash] params Params for Couchdb query. Some useful are:
177
+ # :start_key, :start_key_doc_id, :descending. See {View#fetch}.
178
+ #
179
+ # @example Use each method with block
180
+ #
181
+ # view.each do |doc|
182
+ # # do something with doc
183
+ # end
184
+ #
185
+ # @example Use Enumerator version
186
+ #
187
+ # enum = view.each # request hasn't issued yet
188
+ # enum.map{|doc| doc.title.upcase}
189
+ #
190
+ # @example Pass options during view initialization
191
+ #
192
+ # endpoint = "http://localhost:5984/default/_design/blog/_view/recent"
193
+ # view = View.new(conn, endpoint, :descending => true)
194
+ # view.each do |document|
195
+ # # do something with document
196
+ # end
197
+ #
198
+ def each(params = {})
199
+ return enum_for(:each, params) unless block_given?
200
+ fetch(params) {|doc| yield(doc)}
201
+ end
202
+
203
+ def first(params = {})
204
+ params = params.merge(:limit => 1)
205
+ fetch(params).first
206
+ end
207
+
208
+ def take(n, params = {})
209
+ params = params.merge(:limit => n)
210
+ fetch(params)
211
+ end
212
+
213
+ # Registers callback function for handling error objects in view
214
+ # results stream.
215
+ #
216
+ # @yieldparam [String] from Location of the node where error occured
217
+ # @yieldparam [String] reason The reason message describing what
218
+ # happened.
219
+ #
220
+ # @example Using +#on_error+ to log all errors in view result
221
+ #
222
+ # # JSON-encoded view result
223
+ # #
224
+ # # {
225
+ # # "total_rows": 0,
226
+ # # "rows": [ ],
227
+ # # "errors": [
228
+ # # {
229
+ # # "from": "127.0.0.1:5984",
230
+ # # "reason": "Design document `_design/testfoobar` missing in database `test_db_b`."
231
+ # # },
232
+ # # {
233
+ # # "from": "http:// localhost:5984/_view_merge/",
234
+ # # "reason": "Design document `_design/testfoobar` missing in database `test_db_c`."
235
+ # # }
236
+ # # ]
237
+ # # }
238
+ #
239
+ # view.on_error do |from, reason|
240
+ # logger.warn("#{view.inspect} received the error '#{reason}' from #{from}")
241
+ # end
242
+ # docs = view.fetch
243
+ #
244
+ # @example More concise example to just count errors
245
+ #
246
+ # errcount = 0
247
+ # view.on_error{|f,r| errcount += 1}.fetch
248
+ #
249
+ def on_error(&callback)
250
+ @on_error = callback
251
+ self # enable call chains
252
+ end
253
+
254
+ # Performs query to Couchbase view. This method will stream results if block
255
+ # given or return complete result set otherwise. In latter case it defines
256
+ # method +total_rows+ returning corresponding entry from
257
+ # Couchbase result object.
258
+ #
259
+ # @note Avoid using +$+ symbol as prefix for properties in your
260
+ # documents, because server marks with it meta fields like flags and
261
+ # expiration, therefore dollar prefix is some kind of reserved. It
262
+ # won't hurt your application. Currently the {ViewRow}
263
+ # class extracts +$flags+, +$cas+ and +$expiration+ properties from
264
+ # the document and store them in {ViewRow#meta} hash.
265
+ #
266
+ # @param [Hash] params parameters for Couchbase query.
267
+ # @option params [true, false] :include_docs (false) Include the
268
+ # full content of the documents in the return. Note that the document
269
+ # is fetched from the in memory cache where it may have been changed
270
+ # or even deleted. See also +:quiet+ parameter below to control error
271
+ # reporting during fetch.
272
+ # @option params [true, false] :quiet (true) Do not raise error if
273
+ # associated document not found in the memory. If the parameter +true+
274
+ # will use +nil+ value instead.
275
+ # @option params [true, false] :descending (false) Return the documents
276
+ # in descending by key order
277
+ # @option params [String, Fixnum, Hash, Array] :key Return only
278
+ # documents that match the specified key. Will be JSON encoded.
279
+ # @option params [Array] :keys The same as +:key+, but will work for
280
+ # set of keys. Will be JSON encoded.
281
+ # @option params [String, Fixnum, Hash, Array] :startkey Return
282
+ # records starting with the specified key. +:start_key+ option should
283
+ # also work here. Will be JSON encoded.
284
+ # @option params [String] :startkey_docid Document id to start with
285
+ # (to allow pagination for duplicate startkeys). +:start_key_doc_id+
286
+ # also should work.
287
+ # @option params [String, Fixnum, Hash, Array] :endkey Stop returning
288
+ # records when the specified key is reached. +:end_key+ option should
289
+ # also work here. Will be JSON encoded.
290
+ # @option params [String] :endkey_docid Last document id to include
291
+ # in the output (to allow pagination for duplicate startkeys).
292
+ # +:end_key_doc_id+ also should work.
293
+ # @option params [true, false] :inclusive_end (true) Specifies whether
294
+ # the specified end key should be included in the result
295
+ # @option params [Fixnum] :limit Limit the number of documents in the
296
+ # output.
297
+ # @option params [Fixnum] :skip Skip this number of records before
298
+ # starting to return the results.
299
+ # @option params [String, Symbol] :on_error (:continue) Sets the
300
+ # response in the event of an error. Supported values:
301
+ # :continue:: Continue to generate view information in the event of an
302
+ # error, including the error information in the view
303
+ # response stream.
304
+ # :stop:: Stop immediately when an error condition occurs. No
305
+ # further view information will be returned.
306
+ # @option params [Fixnum] :connection_timeout (75000) Timeout before the
307
+ # view request is dropped (milliseconds)
308
+ # @option params [true, false] :reduce (true) Use the reduction function
309
+ # @option params [true, false] :group (false) Group the results using
310
+ # the reduce function to a group or single row.
311
+ # @option params [Fixnum] :group_level Specify the group level to be
312
+ # used.
313
+ # @option params [String, Symbol, false] :stale (:update_after) Allow
314
+ # the results from a stale view to be used. Supported values:
315
+ # false:: Force a view update before returning data
316
+ # :ok:: Allow stale views
317
+ # :update_after:: Allow stale view, update view after it has been
318
+ # accessed
319
+ # @option params [Hash] :body Accepts the same parameters, except
320
+ # +:body+ of course, but sends them in POST body instead of query
321
+ # string. It could be useful for really large and complex parameters.
322
+ #
323
+ # @yieldparam [Couchbase::ViewRow] document
324
+ #
325
+ # @return [Array] with documents. There will be +total_entries+
326
+ # method defined on this array if it's possible.
327
+ #
328
+ # @raise [Couchbase::Error::View] when +on_error+ callback is nil and
329
+ # error object found in the result stream.
330
+ #
331
+ # @example Query +recent_posts+ view with key filter
332
+ # doc.recent_posts(:body => {:keys => ["key1", "key2"]})
333
+ #
334
+ # @example Fetch second page of result set (splitted in 10 items per page)
335
+ # page = 2
336
+ # per_page = 10
337
+ # doc.recent_posts(:skip => (page - 1) * per_page, :limit => per_page)
338
+ #
339
+ # @example Simple join using Map/Reduce
340
+ # # Given the bucket with Posts(:id, :type, :title, :body) and
341
+ # # Comments(:id, :type, :post_id, :author, :body). The map function
342
+ # # below (in javascript) will build the View index called
343
+ # # "recent_posts_with_comments" which will behave like left inner join.
344
+ # #
345
+ # # function(doc) {
346
+ # # switch (doc.type) {
347
+ # # case "Post":
348
+ # # emit([doc.id, 0], null);
349
+ # # break;
350
+ # # case "Comment":
351
+ # # emit([doc.post_id, 1], null);
352
+ # # break;
353
+ # # }
354
+ # # }
355
+ # #
356
+ # post_id = 42
357
+ # doc.recent_posts_with_comments(:start_key => [post_id, 0],
358
+ # :end_key => [post_id, 1],
359
+ # :include_docs => true)
360
+ def fetch(params = {}, &block)
361
+ params = @params.merge(params)
362
+ include_docs = params.delete(:include_docs)
363
+ quiet = params.delete(:quiet){ true }
364
+
365
+ options = {:chunked => true, :extended => true, :type => :view}
366
+ if body = params.delete(:body)
367
+ body = MultiJson.dump(body) unless body.is_a?(String)
368
+ options.update(:body => body, :method => params.delete(:method) || :post)
369
+ end
370
+ path = Utils.build_query(@endpoint, params)
371
+ request = @bucket.make_http_request(path, options)
372
+
373
+ if @bucket.async?
374
+ if block
375
+ fetch_async(request, include_docs, quiet, block)
376
+ end
377
+ else
378
+ fetch_sync(request, include_docs, quiet, block)
379
+ end
380
+ end
381
+
382
+ # Method for fetching asynchronously all rows and passing array to callback
383
+ #
384
+ # Parameters are same as for {View#fetch} method, but callback is called for whole set for
385
+ # rows instead of one by each.
386
+ #
387
+ # @example
388
+ # con.run do
389
+ # doc.recent_posts.fetch_all do |posts|
390
+ # do_something_with_all_posts(posts)
391
+ # end
392
+ # end
393
+ def fetch_all(params = {}, &block)
394
+ return fetch(params) unless @bucket.async?
395
+ raise ArgumentError, "Block needed for fetch_all in async mode" unless block
396
+
397
+ all = []
398
+ fetch(params) do |row|
399
+ all << row
400
+ if row.last?
401
+ @bucket.create_timer(0) { block.call(all) }
402
+ end
403
+ end
404
+ end
405
+
406
+
407
+ # Returns a string containing a human-readable representation of the {View}
408
+ #
409
+ # @return [String]
410
+ def inspect
411
+ %(#<#{self.class.name}:#{self.object_id} @endpoint=#{@endpoint.inspect} @params=#{@params.inspect}>)
412
+ end
413
+
414
+ private
415
+
416
+ def send_error(*args)
417
+ if @on_error
418
+ @on_error.call(*args.take(2))
419
+ else
420
+ raise Error::View.new(*args)
421
+ end
422
+ end
423
+
424
+ def fetch_async(request, include_docs, quiet, block)
425
+ filter = ["/rows/", "/errors/"]
426
+ parser = YAJI::Parser.new(:filter => filter, :with_path => true)
427
+ helper = AsyncHelper.new(@wrapper_class, @bucket, include_docs, quiet, block)
428
+
429
+ request.on_body do |chunk|
430
+ if chunk.success?
431
+ parser << chunk.value if chunk.value
432
+ helper.complete! if chunk.completed?
433
+ else
434
+ send_error("http_error", chunk.error)
435
+ end
436
+ end
437
+
438
+ parser.on_object do |path, obj|
439
+ case path
440
+ when "/errors/"
441
+ from, reason = obj["from"], obj["reason"]
442
+ send_error(from, reason)
443
+ else
444
+ helper.push(obj)
445
+ end
446
+ end
447
+
448
+ request.perform
449
+ nil
450
+ end
451
+
452
+ def fetch_sync(request, include_docs, quiet, block)
453
+ res = []
454
+ filter = ["/rows/", "/errors/"]
455
+ unless block
456
+ filter << "/total_rows"
457
+ docs = ArrayWithTotalRows.new
458
+ end
459
+ parser = YAJI::Parser.new(:filter => filter, :with_path => true)
460
+ last_chunk = nil
461
+
462
+ request.on_body do |chunk|
463
+ last_chunk = chunk
464
+ res << chunk.value if chunk.success?
465
+ end
466
+
467
+ parser.on_object do |path, obj|
468
+ case path
469
+ when "/total_rows"
470
+ # if total_rows key present, save it and take next object
471
+ docs.total_rows = obj
472
+ when "/errors/"
473
+ from, reason = obj["from"], obj["reason"]
474
+ send_error(from, reason)
475
+ else
476
+ if include_docs
477
+ val, flags, cas = @bucket.get(obj[S_ID], :extended => true, :quiet => quiet)
478
+ obj[S_DOC] = {
479
+ S_VALUE => val,
480
+ S_META => {
481
+ S_ID => obj[S_ID],
482
+ S_FLAGS => flags,
483
+ S_CAS => cas
484
+ }
485
+ }
486
+ end
487
+ doc = @wrapper_class.wrap(@bucket, obj)
488
+ block ? block.call(doc) : docs << doc
489
+ end
490
+ end
491
+
492
+ request.continue
493
+
494
+ if last_chunk.success?
495
+ while value = res.shift
496
+ parser << value
497
+ end
498
+ else
499
+ send_error("http_error", last_chunk.error, nil)
500
+ end
501
+
502
+ # return nil for call with block
503
+ docs
504
+ end
505
+ end
506
+ end