couch-db 0.11.1 → 1.0.0
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.
- checksums.yaml +4 -4
- data/.gitignore +12 -12
- data/.rspec +2 -2
- data/.travis.yml +7 -7
- data/CODE_OF_CONDUCT.md +13 -13
- data/Gemfile +4 -4
- data/LICENSE.txt +21 -21
- data/README.md +122 -116
- data/Rakefile +6 -6
- data/bin/console +14 -14
- data/bin/setup +7 -7
- data/couch-request.gemspec +39 -39
- data/lib/couch.rb +413 -410
- data/lib/couch/db/version.rb +6 -6
- metadata +3 -3
data/lib/couch.rb
CHANGED
|
@@ -1,410 +1,413 @@
|
|
|
1
|
-
require 'couch/db/version'
|
|
2
|
-
require 'net/http'
|
|
3
|
-
require 'json'
|
|
4
|
-
require 'objspace'
|
|
5
|
-
require 'openssl'
|
|
6
|
-
|
|
7
|
-
class Hash
|
|
8
|
-
def include_symbol_or_string?(param)
|
|
9
|
-
if param.is_a? Symbol or param.is_a? String
|
|
10
|
-
include? param.to_sym or include? param.to_s
|
|
11
|
-
else
|
|
12
|
-
false
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
module Couch
|
|
18
|
-
module BasicRequest
|
|
19
|
-
def create_postfix(query_params, default='')
|
|
20
|
-
if query_params
|
|
21
|
-
params_a = []
|
|
22
|
-
query_params.each do |key, value|
|
|
23
|
-
params_a << "#{key}=#{value}"
|
|
24
|
-
end
|
|
25
|
-
postfix = "?#{params_a.join('&')}"
|
|
26
|
-
else
|
|
27
|
-
postfix = default
|
|
28
|
-
end
|
|
29
|
-
postfix
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
module Get
|
|
33
|
-
# Returns parsed doc from database
|
|
34
|
-
def get_doc(database, id)
|
|
35
|
-
res = get("/#{database}/#{CGI.escape(id)}")
|
|
36
|
-
JSON.parse(res.body)
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def get_attachment_str(db, id, attachment)
|
|
40
|
-
uri = URI::encode "/#{db}/#{id}/#{attachment}"
|
|
41
|
-
get(uri).body
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
module Head
|
|
46
|
-
# Returns revision for given document
|
|
47
|
-
def get_rev(database, id)
|
|
48
|
-
res = head("/#{database}/#{CGI.escape(id)}")
|
|
49
|
-
if res.code == '200'
|
|
50
|
-
res['etag'].gsub(/^"|"$/, '')
|
|
51
|
-
else
|
|
52
|
-
nil
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# Bulk requests; use methods from Couch::BasicRequest
|
|
59
|
-
module BulkRequest
|
|
60
|
-
module Get
|
|
61
|
-
# Returns an array of the full documents for given database, possibly filtered with given parameters.
|
|
62
|
-
# We recommend you use all_docs instead.
|
|
63
|
-
#
|
|
64
|
-
# Note that the 'include_docs' parameter must be set to true for this.
|
|
65
|
-
def get_all_docs(database, params)
|
|
66
|
-
# unless params.include_symbol_or_string? :include_docs
|
|
67
|
-
# params.merge!({:include_docs => true})
|
|
68
|
-
# end
|
|
69
|
-
postfix = create_postfix(params)
|
|
70
|
-
uri = URI::encode "/#{database}/_all_docs#{postfix}"
|
|
71
|
-
res = get(uri)
|
|
72
|
-
JSON.parse(res.body)['rows']
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
# If a block is given, performs the block for each +limit+-sized slice of _all_docs.
|
|
77
|
-
# If no block is given, returns all docs by appending +limit+-sized slices of _all_docs.
|
|
78
|
-
#
|
|
79
|
-
# This method assumes your docs don't have the high-value Unicode character \ufff0. If it does, then behaviour is undefined. The reason why we use the startkey parameter instead of skip is that startkey is faster.
|
|
80
|
-
def all_docs(db, limit=750, opts={}, &block)
|
|
81
|
-
all_docs = []
|
|
82
|
-
start_key = nil
|
|
83
|
-
loop do
|
|
84
|
-
opts = opts.merge({limit: limit})
|
|
85
|
-
if start_key
|
|
86
|
-
opts[:startkey]=start_key
|
|
87
|
-
end
|
|
88
|
-
docs = (lambda { |options| get_all_docs(db, options) }).call(opts)
|
|
89
|
-
if docs.length <= 0
|
|
90
|
-
break
|
|
91
|
-
else
|
|
92
|
-
if block
|
|
93
|
-
block.call(docs)
|
|
94
|
-
else
|
|
95
|
-
all_docs < docs
|
|
96
|
-
end
|
|
97
|
-
start_key ="\"#{docs.last['_id']}\\ufff0\""
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
all_docs.flatten
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
# Returns an array of all rows for given view.
|
|
104
|
-
#
|
|
105
|
-
# We recommend you use rows_for_view instead.
|
|
106
|
-
def get_rows_for_view(database, design_doc, view, query_params=nil)
|
|
107
|
-
postfix = create_postfix(query_params)
|
|
108
|
-
uri = URI::encode "/#{database}/_design/#{design_doc}/_view/#{view}#{postfix}"
|
|
109
|
-
res = get(uri)
|
|
110
|
-
JSON.parse(res.body.force_encoding('utf-8'))['rows']
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
# If a block is given, performs the block for each +limit+-sized slice of rows for the given view.
|
|
114
|
-
# If no block is given, returns all rows by appending +limit+-sized slices of the given view.
|
|
115
|
-
def rows_for_view(db, design_doc, view, limit=500, opts={}, &block)
|
|
116
|
-
get_all_views(lambda { |options| get_rows_for_view(db, design_doc, view, options) }, limit, opts, block)
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
# Returns an array of all ids in the database
|
|
121
|
-
def get_all_ids(database, params)
|
|
122
|
-
ids=[]
|
|
123
|
-
postfix = create_postfix(params)
|
|
124
|
-
|
|
125
|
-
uri = URI::encode "/#{database}/_all_docs#{postfix}"
|
|
126
|
-
res = get(uri)
|
|
127
|
-
result = JSON.parse(res.body)
|
|
128
|
-
result['rows'].each do |row|
|
|
129
|
-
if row['error']
|
|
130
|
-
puts "#{row['key']}: #{row['error']}"
|
|
131
|
-
puts "#{row['reason']}"
|
|
132
|
-
else
|
|
133
|
-
ids << row['id']
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
|
-
ids
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
# Returns an array of all ids in the database
|
|
140
|
-
def all_ids(db, limit=500, opts={}, &block)
|
|
141
|
-
all_docs = []
|
|
142
|
-
start_key = nil
|
|
143
|
-
loop do
|
|
144
|
-
opts = opts.merge({limit: limit})
|
|
145
|
-
if start_key
|
|
146
|
-
opts[:startkey]=start_key
|
|
147
|
-
end
|
|
148
|
-
docs = (lambda { |options| get_all_ids(db, options) }).call(opts)
|
|
149
|
-
if docs.length <= 0
|
|
150
|
-
break
|
|
151
|
-
else
|
|
152
|
-
if block
|
|
153
|
-
block.call(docs)
|
|
154
|
-
else
|
|
155
|
-
all_docs < docs
|
|
156
|
-
end
|
|
157
|
-
start_key ="\"#{docs.last}\\ufff0\""
|
|
158
|
-
end
|
|
159
|
-
end
|
|
160
|
-
all_docs.flatten
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
# Returns an array of the full documents for given view, possibly filtered with given parameters. Note that the 'include_docs' parameter must be set to true for this.
|
|
164
|
-
#
|
|
165
|
-
# Also consider using `docs_for_view`
|
|
166
|
-
def get_docs_for_view(db, design_doc, view, params={})
|
|
167
|
-
params.merge!({:include_docs => true})
|
|
168
|
-
rows = get_rows_for_view(db, design_doc, view, params)
|
|
169
|
-
docs = []
|
|
170
|
-
rows.each do |row|
|
|
171
|
-
docs << row['doc']
|
|
172
|
-
end
|
|
173
|
-
docs
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
# If a block is given, performs the block for each +limit+-sized slice of documents for the given view.
|
|
177
|
-
# If no block is given, returns all docs by appending +limit+-sized slices of the given view.
|
|
178
|
-
def docs_for_view(db, design_doc, view, limit=750, opts={}, &block)
|
|
179
|
-
get_all_views(lambda { |options| get_docs_for_view(db, design_doc, view, options) }, limit, opts, block)
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
private
|
|
183
|
-
|
|
184
|
-
def get_all_views(next_results, limit, opts, block)
|
|
185
|
-
all = []
|
|
186
|
-
offset = 0
|
|
187
|
-
loop do
|
|
188
|
-
opts = opts.merge({
|
|
189
|
-
limit: limit,
|
|
190
|
-
skip: offset,
|
|
191
|
-
})
|
|
192
|
-
docs = next_results.call(opts)
|
|
193
|
-
if docs.length <= 0
|
|
194
|
-
break
|
|
195
|
-
else
|
|
196
|
-
if block
|
|
197
|
-
block.call(docs)
|
|
198
|
-
else
|
|
199
|
-
all < docs
|
|
200
|
-
end
|
|
201
|
-
offset += limit
|
|
202
|
-
end
|
|
203
|
-
end
|
|
204
|
-
all.flatten
|
|
205
|
-
end
|
|
206
|
-
|
|
207
|
-
end
|
|
208
|
-
|
|
209
|
-
module Delete
|
|
210
|
-
def bulk_delete(database, docs)
|
|
211
|
-
docs.each do |doc|
|
|
212
|
-
doc[:_deleted]=true
|
|
213
|
-
end
|
|
214
|
-
json = {:docs => docs}.to_json
|
|
215
|
-
post("/#{database}/_bulk_docs", json)
|
|
216
|
-
end
|
|
217
|
-
end
|
|
218
|
-
|
|
219
|
-
module Post
|
|
220
|
-
# Flushes the given hashes to CouchDB
|
|
221
|
-
def post_bulk(database, docs)
|
|
222
|
-
body = {:docs => docs}.to_json #.force_encoding('utf-8')
|
|
223
|
-
post("/#{database}/_bulk_docs", body)
|
|
224
|
-
end
|
|
225
|
-
|
|
226
|
-
def post_bulk_throttled(db, docs, &block)
|
|
227
|
-
# puts "Flushing #{docs.length} docs"
|
|
228
|
-
bulk = []
|
|
229
|
-
docs.each do |doc|
|
|
230
|
-
bulk << doc
|
|
231
|
-
if bulk.to_json.bytesize/1024/1024 > options[:flush_size_mb] or bulk.length >= options[:max_array_length]
|
|
232
|
-
handle_bulk_flush(bulk, db, block)
|
|
233
|
-
end
|
|
234
|
-
end
|
|
235
|
-
if bulk.length > 0
|
|
236
|
-
handle_bulk_flush(bulk, db, block)
|
|
237
|
-
end
|
|
238
|
-
end
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
def post_bulk_if_big_enough(db, docs)
|
|
242
|
-
flush = (docs.to_json.bytesize / 1024 >= (options[:flush_size_mb]*1024) or docs.length >= options[:max_array_length])
|
|
243
|
-
if flush
|
|
244
|
-
post_bulk_throttled(db, docs)
|
|
245
|
-
docs.clear
|
|
246
|
-
end
|
|
247
|
-
flush
|
|
248
|
-
end
|
|
249
|
-
|
|
250
|
-
private
|
|
251
|
-
|
|
252
|
-
def handle_bulk_flush(bulk, db, block)
|
|
253
|
-
res = post_bulk(db, bulk)
|
|
254
|
-
error_count=0
|
|
255
|
-
if res.body
|
|
256
|
-
begin
|
|
257
|
-
JSON.parse(res.body).each do |d|
|
|
258
|
-
error_count+=1 if d['error']
|
|
259
|
-
end
|
|
260
|
-
end
|
|
261
|
-
end
|
|
262
|
-
if error_count > 0
|
|
263
|
-
puts "Bulk request completed with #{error_count} errors"
|
|
264
|
-
end
|
|
265
|
-
if block
|
|
266
|
-
block.call(res)
|
|
267
|
-
end
|
|
268
|
-
bulk.clear
|
|
269
|
-
end
|
|
270
|
-
end
|
|
271
|
-
end
|
|
272
|
-
|
|
273
|
-
class Server
|
|
274
|
-
attr_accessor :options
|
|
275
|
-
|
|
276
|
-
def initialize(url, options)
|
|
277
|
-
if url.is_a? String
|
|
278
|
-
url = URI(url)
|
|
279
|
-
end
|
|
280
|
-
@couch_url = url
|
|
281
|
-
@options = options
|
|
282
|
-
@options[:couch_url] = @couch_url
|
|
283
|
-
@options[:use_ssl] ||= true
|
|
284
|
-
@options[:max_array_length] ||= 250
|
|
285
|
-
@options[:flush_size_mb] ||= 10
|
|
286
|
-
@options[:open_timeout] ||= 5*30
|
|
287
|
-
@options[:read_timeout] ||= 5*30
|
|
288
|
-
@options[:fail_silent] ||= false
|
|
289
|
-
end
|
|
290
|
-
|
|
291
|
-
def delete(uri)
|
|
292
|
-
Request.new(Net::HTTP::Delete.new(uri), nil,
|
|
293
|
-
@options
|
|
294
|
-
).perform
|
|
295
|
-
end
|
|
296
|
-
|
|
297
|
-
def new_delete(uri)
|
|
298
|
-
Request.new(Net::HTTP::Delete.new(uri)).couch_url(@couch_url)
|
|
299
|
-
end
|
|
300
|
-
|
|
301
|
-
def head(uri)
|
|
302
|
-
Request.new(Net::HTTP::Head.new(uri), nil,
|
|
303
|
-
@options
|
|
304
|
-
).perform
|
|
305
|
-
end
|
|
306
|
-
|
|
307
|
-
def new_head(uri)
|
|
308
|
-
Request.new(Net::HTTP::Head.new(uri)).couch_url(@couch_url)
|
|
309
|
-
end
|
|
310
|
-
|
|
311
|
-
def get(uri)
|
|
312
|
-
Request.new(
|
|
313
|
-
Net::HTTP::Get.new(uri), nil,
|
|
314
|
-
@options
|
|
315
|
-
).perform
|
|
316
|
-
end
|
|
317
|
-
|
|
318
|
-
def new_get(uri)
|
|
319
|
-
Request.new(Net::HTTP::Get.new(uri)).couch_url(@couch_url)
|
|
320
|
-
end
|
|
321
|
-
|
|
322
|
-
def put(uri, json)
|
|
323
|
-
Request.new(Net::HTTP::Put.new(uri), json,
|
|
324
|
-
@options
|
|
325
|
-
).perform
|
|
326
|
-
end
|
|
327
|
-
|
|
328
|
-
def new_put(uri)
|
|
329
|
-
Request.new(Net::HTTP::Put.new(uri)).couch_url(@couch_url)
|
|
330
|
-
end
|
|
331
|
-
|
|
332
|
-
def post(uri, json)
|
|
333
|
-
Request.new(Net::HTTP::Post.new(uri), json,
|
|
334
|
-
@options
|
|
335
|
-
).perform
|
|
336
|
-
end
|
|
337
|
-
|
|
338
|
-
def new_post(uri)
|
|
339
|
-
Request.new(Net::HTTP::Post.new(uri)).couch_url(@couch_url)
|
|
340
|
-
end
|
|
341
|
-
|
|
342
|
-
class Request
|
|
343
|
-
def initialize(req, json=nil, opts={open_timeout: 5*30, read_timeout: 5*30, fail_silent: false})
|
|
344
|
-
@req=req
|
|
345
|
-
@json = json
|
|
346
|
-
@options = opts
|
|
347
|
-
end
|
|
348
|
-
|
|
349
|
-
def json(json)
|
|
350
|
-
@json = json
|
|
351
|
-
self
|
|
352
|
-
end
|
|
353
|
-
|
|
354
|
-
def couch_url(couch_url)
|
|
355
|
-
@options.merge!({couch_url: couch_url})
|
|
356
|
-
self
|
|
357
|
-
end
|
|
358
|
-
|
|
359
|
-
def open_timeout(open_timeout)
|
|
360
|
-
@options.merge!({open_timeout: open_timeout})
|
|
361
|
-
self
|
|
362
|
-
end
|
|
363
|
-
|
|
364
|
-
def read_timeout(read_timeout)
|
|
365
|
-
@options.merge!({read_timeout: read_timeout})
|
|
366
|
-
self
|
|
367
|
-
end
|
|
368
|
-
|
|
369
|
-
def fail_silent(fail_silent)
|
|
370
|
-
@options.merge!({fail_silent: fail_silent})
|
|
371
|
-
self
|
|
372
|
-
end
|
|
373
|
-
|
|
374
|
-
def perform
|
|
375
|
-
@req.basic_auth @options[:name], @options[:password]
|
|
376
|
-
|
|
377
|
-
if @json
|
|
378
|
-
@req['Content-Type'] = 'application/json;charset=utf-8'
|
|
379
|
-
@req.body = @json
|
|
380
|
-
end
|
|
381
|
-
|
|
382
|
-
res = Net::HTTP.start(
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
include
|
|
405
|
-
include
|
|
406
|
-
include
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
1
|
+
require 'couch/db/version'
|
|
2
|
+
require 'net/http'
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'objspace'
|
|
5
|
+
require 'openssl'
|
|
6
|
+
|
|
7
|
+
class Hash
|
|
8
|
+
def include_symbol_or_string?(param)
|
|
9
|
+
if param.is_a? Symbol or param.is_a? String
|
|
10
|
+
include? param.to_sym or include? param.to_s
|
|
11
|
+
else
|
|
12
|
+
false
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
module Couch
|
|
18
|
+
module BasicRequest
|
|
19
|
+
def create_postfix(query_params, default='')
|
|
20
|
+
if query_params
|
|
21
|
+
params_a = []
|
|
22
|
+
query_params.each do |key, value|
|
|
23
|
+
params_a << "#{key}=#{value}"
|
|
24
|
+
end
|
|
25
|
+
postfix = "?#{params_a.join('&')}"
|
|
26
|
+
else
|
|
27
|
+
postfix = default
|
|
28
|
+
end
|
|
29
|
+
postfix
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
module Get
|
|
33
|
+
# Returns parsed doc from database
|
|
34
|
+
def get_doc(database, id, params={})
|
|
35
|
+
res = get("/#{database}/#{CGI.escape(id)}#{create_postfix(params)}")
|
|
36
|
+
JSON.parse(res.body)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def get_attachment_str(db, id, attachment)
|
|
40
|
+
uri = URI::encode "/#{db}/#{id}/#{attachment}"
|
|
41
|
+
get(uri).body
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
module Head
|
|
46
|
+
# Returns revision for given document
|
|
47
|
+
def get_rev(database, id)
|
|
48
|
+
res = head("/#{database}/#{CGI.escape(id)}")
|
|
49
|
+
if res.code == '200'
|
|
50
|
+
res['etag'].gsub(/^"|"$/, '')
|
|
51
|
+
else
|
|
52
|
+
nil
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Bulk requests; use methods from Couch::BasicRequest
|
|
59
|
+
module BulkRequest
|
|
60
|
+
module Get
|
|
61
|
+
# Returns an array of the full documents for given database, possibly filtered with given parameters.
|
|
62
|
+
# We recommend you use all_docs instead.
|
|
63
|
+
#
|
|
64
|
+
# Note that the 'include_docs' parameter must be set to true for this.
|
|
65
|
+
def get_all_docs(database, params)
|
|
66
|
+
# unless params.include_symbol_or_string? :include_docs
|
|
67
|
+
# params.merge!({:include_docs => true})
|
|
68
|
+
# end
|
|
69
|
+
postfix = create_postfix(params)
|
|
70
|
+
uri = URI::encode "/#{database}/_all_docs#{postfix}"
|
|
71
|
+
res = get(uri)
|
|
72
|
+
JSON.parse(res.body)['rows']
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# If a block is given, performs the block for each +limit+-sized slice of _all_docs.
|
|
77
|
+
# If no block is given, returns all docs by appending +limit+-sized slices of _all_docs.
|
|
78
|
+
#
|
|
79
|
+
# This method assumes your docs don't have the high-value Unicode character \ufff0. If it does, then behaviour is undefined. The reason why we use the startkey parameter instead of skip is that startkey is faster.
|
|
80
|
+
def all_docs(db, limit=750, opts={}, &block)
|
|
81
|
+
all_docs = []
|
|
82
|
+
start_key = nil
|
|
83
|
+
loop do
|
|
84
|
+
opts = opts.merge({limit: limit})
|
|
85
|
+
if start_key
|
|
86
|
+
opts[:startkey]=start_key
|
|
87
|
+
end
|
|
88
|
+
docs = (lambda { |options| get_all_docs(db, options) }).call(opts)
|
|
89
|
+
if docs.length <= 0
|
|
90
|
+
break
|
|
91
|
+
else
|
|
92
|
+
if block
|
|
93
|
+
block.call(docs)
|
|
94
|
+
else
|
|
95
|
+
all_docs < docs
|
|
96
|
+
end
|
|
97
|
+
start_key ="\"#{docs.last['_id']}\\ufff0\""
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
all_docs.flatten
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Returns an array of all rows for given view.
|
|
104
|
+
#
|
|
105
|
+
# We recommend you use rows_for_view instead.
|
|
106
|
+
def get_rows_for_view(database, design_doc, view, query_params=nil)
|
|
107
|
+
postfix = create_postfix(query_params)
|
|
108
|
+
uri = URI::encode "/#{database}/_design/#{design_doc}/_view/#{view}#{postfix}"
|
|
109
|
+
res = get(uri)
|
|
110
|
+
JSON.parse(res.body.force_encoding('utf-8'))['rows']
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# If a block is given, performs the block for each +limit+-sized slice of rows for the given view.
|
|
114
|
+
# If no block is given, returns all rows by appending +limit+-sized slices of the given view.
|
|
115
|
+
def rows_for_view(db, design_doc, view, limit=500, opts={}, &block)
|
|
116
|
+
get_all_views(lambda { |options| get_rows_for_view(db, design_doc, view, options) }, limit, opts, block)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# Returns an array of all ids in the database
|
|
121
|
+
def get_all_ids(database, params)
|
|
122
|
+
ids=[]
|
|
123
|
+
postfix = create_postfix(params)
|
|
124
|
+
|
|
125
|
+
uri = URI::encode "/#{database}/_all_docs#{postfix}"
|
|
126
|
+
res = get(uri)
|
|
127
|
+
result = JSON.parse(res.body)
|
|
128
|
+
result['rows'].each do |row|
|
|
129
|
+
if row['error']
|
|
130
|
+
puts "#{row['key']}: #{row['error']}"
|
|
131
|
+
puts "#{row['reason']}"
|
|
132
|
+
else
|
|
133
|
+
ids << row['id']
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
ids
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Returns an array of all ids in the database
|
|
140
|
+
def all_ids(db, limit=500, opts={}, &block)
|
|
141
|
+
all_docs = []
|
|
142
|
+
start_key = nil
|
|
143
|
+
loop do
|
|
144
|
+
opts = opts.merge({limit: limit})
|
|
145
|
+
if start_key
|
|
146
|
+
opts[:startkey]=start_key
|
|
147
|
+
end
|
|
148
|
+
docs = (lambda { |options| get_all_ids(db, options) }).call(opts)
|
|
149
|
+
if docs.length <= 0
|
|
150
|
+
break
|
|
151
|
+
else
|
|
152
|
+
if block
|
|
153
|
+
block.call(docs)
|
|
154
|
+
else
|
|
155
|
+
all_docs < docs
|
|
156
|
+
end
|
|
157
|
+
start_key ="\"#{docs.last}\\ufff0\""
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
all_docs.flatten
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Returns an array of the full documents for given view, possibly filtered with given parameters. Note that the 'include_docs' parameter must be set to true for this.
|
|
164
|
+
#
|
|
165
|
+
# Also consider using `docs_for_view`
|
|
166
|
+
def get_docs_for_view(db, design_doc, view, params={})
|
|
167
|
+
params.merge!({:include_docs => true})
|
|
168
|
+
rows = get_rows_for_view(db, design_doc, view, params)
|
|
169
|
+
docs = []
|
|
170
|
+
rows.each do |row|
|
|
171
|
+
docs << row['doc']
|
|
172
|
+
end
|
|
173
|
+
docs
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# If a block is given, performs the block for each +limit+-sized slice of documents for the given view.
|
|
177
|
+
# If no block is given, returns all docs by appending +limit+-sized slices of the given view.
|
|
178
|
+
def docs_for_view(db, design_doc, view, limit=750, opts={}, &block)
|
|
179
|
+
get_all_views(lambda { |options| get_docs_for_view(db, design_doc, view, options) }, limit, opts, block)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
private
|
|
183
|
+
|
|
184
|
+
def get_all_views(next_results, limit, opts, block)
|
|
185
|
+
all = []
|
|
186
|
+
offset = 0
|
|
187
|
+
loop do
|
|
188
|
+
opts = opts.merge({
|
|
189
|
+
limit: limit,
|
|
190
|
+
skip: offset,
|
|
191
|
+
})
|
|
192
|
+
docs = next_results.call(opts)
|
|
193
|
+
if docs.length <= 0
|
|
194
|
+
break
|
|
195
|
+
else
|
|
196
|
+
if block
|
|
197
|
+
block.call(docs)
|
|
198
|
+
else
|
|
199
|
+
all < docs
|
|
200
|
+
end
|
|
201
|
+
offset += limit
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
all.flatten
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
module Delete
|
|
210
|
+
def bulk_delete(database, docs)
|
|
211
|
+
docs.each do |doc|
|
|
212
|
+
doc[:_deleted]=true
|
|
213
|
+
end
|
|
214
|
+
json = {:docs => docs}.to_json
|
|
215
|
+
post("/#{database}/_bulk_docs", json)
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
module Post
|
|
220
|
+
# Flushes the given hashes to CouchDB
|
|
221
|
+
def post_bulk(database, docs)
|
|
222
|
+
body = {:docs => docs}.to_json #.force_encoding('utf-8')
|
|
223
|
+
post("/#{database}/_bulk_docs", body)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def post_bulk_throttled(db, docs, &block)
|
|
227
|
+
# puts "Flushing #{docs.length} docs"
|
|
228
|
+
bulk = []
|
|
229
|
+
docs.each do |doc|
|
|
230
|
+
bulk << doc
|
|
231
|
+
if bulk.to_json.bytesize/1024/1024 > options[:flush_size_mb] or bulk.length >= options[:max_array_length]
|
|
232
|
+
handle_bulk_flush(bulk, db, block)
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
if bulk.length > 0
|
|
236
|
+
handle_bulk_flush(bulk, db, block)
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def post_bulk_if_big_enough(db, docs)
|
|
242
|
+
flush = (docs.to_json.bytesize / 1024 >= (options[:flush_size_mb]*1024) or docs.length >= options[:max_array_length])
|
|
243
|
+
if flush
|
|
244
|
+
post_bulk_throttled(db, docs)
|
|
245
|
+
docs.clear
|
|
246
|
+
end
|
|
247
|
+
flush
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
private
|
|
251
|
+
|
|
252
|
+
def handle_bulk_flush(bulk, db, block)
|
|
253
|
+
res = post_bulk(db, bulk)
|
|
254
|
+
error_count=0
|
|
255
|
+
if res.body
|
|
256
|
+
begin
|
|
257
|
+
JSON.parse(res.body).each do |d|
|
|
258
|
+
error_count+=1 if d['error']
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
if error_count > 0
|
|
263
|
+
puts "Bulk request completed with #{error_count} errors"
|
|
264
|
+
end
|
|
265
|
+
if block
|
|
266
|
+
block.call(res)
|
|
267
|
+
end
|
|
268
|
+
bulk.clear
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
class Server
|
|
274
|
+
attr_accessor :options
|
|
275
|
+
|
|
276
|
+
def initialize(url, options)
|
|
277
|
+
if url.is_a? String
|
|
278
|
+
url = URI(url)
|
|
279
|
+
end
|
|
280
|
+
@couch_url = url
|
|
281
|
+
@options = options
|
|
282
|
+
@options[:couch_url] = @couch_url
|
|
283
|
+
@options[:use_ssl] ||= true
|
|
284
|
+
@options[:max_array_length] ||= 250
|
|
285
|
+
@options[:flush_size_mb] ||= 10
|
|
286
|
+
@options[:open_timeout] ||= 5*30
|
|
287
|
+
@options[:read_timeout] ||= 5*30
|
|
288
|
+
@options[:fail_silent] ||= false
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def delete(uri)
|
|
292
|
+
Request.new(Net::HTTP::Delete.new(uri), nil,
|
|
293
|
+
@options
|
|
294
|
+
).perform
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def new_delete(uri)
|
|
298
|
+
Request.new(Net::HTTP::Delete.new(uri)).couch_url(@couch_url)
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def head(uri)
|
|
302
|
+
Request.new(Net::HTTP::Head.new(uri), nil,
|
|
303
|
+
@options
|
|
304
|
+
).perform
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def new_head(uri)
|
|
308
|
+
Request.new(Net::HTTP::Head.new(uri)).couch_url(@couch_url)
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def get(uri)
|
|
312
|
+
Request.new(
|
|
313
|
+
Net::HTTP::Get.new(uri), nil,
|
|
314
|
+
@options
|
|
315
|
+
).perform
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def new_get(uri)
|
|
319
|
+
Request.new(Net::HTTP::Get.new(uri)).couch_url(@couch_url)
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def put(uri, json)
|
|
323
|
+
Request.new(Net::HTTP::Put.new(uri), json,
|
|
324
|
+
@options
|
|
325
|
+
).perform
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def new_put(uri)
|
|
329
|
+
Request.new(Net::HTTP::Put.new(uri)).couch_url(@couch_url)
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def post(uri, json)
|
|
333
|
+
Request.new(Net::HTTP::Post.new(uri), json,
|
|
334
|
+
@options
|
|
335
|
+
).perform
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def new_post(uri)
|
|
339
|
+
Request.new(Net::HTTP::Post.new(uri)).couch_url(@couch_url)
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
class Request
|
|
343
|
+
def initialize(req, json=nil, opts={open_timeout: 5*30, read_timeout: 5*30, fail_silent: false})
|
|
344
|
+
@req=req
|
|
345
|
+
@json = json
|
|
346
|
+
@options = opts
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def json(json)
|
|
350
|
+
@json = json
|
|
351
|
+
self
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def couch_url(couch_url)
|
|
355
|
+
@options.merge!({couch_url: couch_url})
|
|
356
|
+
self
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
def open_timeout(open_timeout)
|
|
360
|
+
@options.merge!({open_timeout: open_timeout})
|
|
361
|
+
self
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def read_timeout(read_timeout)
|
|
365
|
+
@options.merge!({read_timeout: read_timeout})
|
|
366
|
+
self
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def fail_silent(fail_silent)
|
|
370
|
+
@options.merge!({fail_silent: fail_silent})
|
|
371
|
+
self
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
def perform
|
|
375
|
+
@req.basic_auth @options[:name], @options[:password]
|
|
376
|
+
|
|
377
|
+
if @json
|
|
378
|
+
@req['Content-Type'] = 'application/json;charset=utf-8'
|
|
379
|
+
@req.body = @json
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
res = Net::HTTP.start(
|
|
383
|
+
@options[:couch_url].host,
|
|
384
|
+
@options[:couch_url].port,
|
|
385
|
+
{:use_ssl => @options[:couch_url].scheme =='https'}
|
|
386
|
+
) do |http|
|
|
387
|
+
http.open_timeout = @options[:open_timeout]
|
|
388
|
+
http.read_timeout = @options[:read_timeout]
|
|
389
|
+
http.request(@req)
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
unless @options[:fail_silent] or res.kind_of?(Net::HTTPSuccess)
|
|
393
|
+
# puts "CouchDb responsed with error code #{res.code}"
|
|
394
|
+
handle_error(@req, res)
|
|
395
|
+
end
|
|
396
|
+
res
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
def handle_error(req, res)
|
|
400
|
+
raise RuntimeError.new("#{res.code}:#{res.message}\nMETHOD:#{req.method}\nURI:#{req.path}\n#{res.body}")
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
include BasicRequest
|
|
405
|
+
include BasicRequest::Head
|
|
406
|
+
include BasicRequest::Get
|
|
407
|
+
include BulkRequest::Get
|
|
408
|
+
include BulkRequest::Delete
|
|
409
|
+
include BulkRequest::Post
|
|
410
|
+
|
|
411
|
+
private
|
|
412
|
+
end
|
|
413
|
+
end
|