couchbase-jruby-client 0.1.0-java

Sign up to get free protection for your applications and to get access to all the features.
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