File Coverage

blib/lib/AnyEvent/CouchDB/Database.pm
Criterion Covered Total %
statement 30 239 12.5
branch 0 40 0.0
condition 0 17 0.0
subroutine 10 47 21.2
pod 25 26 96.1
total 65 369 17.6


line stmt bran cond sub pod time code
1             package AnyEvent::CouchDB::Database;
2              
3 2     2   11 use strict;
  2         4  
  2         82  
4 2     2   11 use warnings;
  2         4  
  2         76  
5 2     2   10 no warnings 'once';
  2         3  
  2         74  
6 2     2   11 use JSON;
  2         2  
  2         21  
7 2     2   290 use AnyEvent::HTTP;
  2         3  
  2         120  
8 2     2   15869 use AnyEvent::CouchDB::Exceptions;
  2         8  
  2         71  
9 2     2   11269 use Data::Dump::Streamer;
  2         233485  
  2         21  
10 2     2   2341 use URI::Escape qw( uri_escape uri_escape_utf8 );
  2         2905  
  2         268  
11 2     2   1957 use IO::All;
  2         51313  
  2         23  
12 2     2   2456 use MIME::Base64;
  2         2110  
  2         5186  
13              
14             our $default_json;
15              
16             # manual import ;-)
17             *cvcb = *AnyEvent::CouchDB::cvcb;
18             *default_json = *AnyEvent::CouchDB::default_json;
19             *_build_headers = *AnyEvent::CouchDB::_build_headers;
20              
21             our $query = sub {
22             my $options = shift;
23             my $json = $default_json;
24             my @buf;
25             if (defined($options) && keys %$options) {
26             for my $name (keys %$options) {
27             next if ($name eq 'error' || $name eq 'success' || $name eq 'headers');
28             my $value = $options->{$name};
29             if ($name eq 'key' || $name eq 'startkey' || $name eq 'endkey') {
30             $value = uri_escape( $json->encode($value) );
31             } else {
32             $value = uri_escape_utf8($value);
33             }
34             if ($name eq 'group' || $name eq 'reduce' || $name eq 'descending' || $name eq 'include_docs') {
35             $value = $value
36             ? ( ($value eq 'false') ? 'false' : 'true' )
37             : 'false';
38             }
39             push @buf, "$name=$value";
40             }
41             }
42             (@buf)
43             ? '?' . join('&', @buf)
44             : '';
45             };
46              
47             our $code_to_string = sub {
48             ref($_[0])
49             ? sprintf 'do { my $CODE1; %s; $CODE1 }',
50             Data::Dump::Streamer->new->Data($_[0])->Out
51             : $_[0];
52             # ^- taken from CouchDB::View::Document ------^
53             };
54              
55             sub new {
56 0     0 1   my ($class, $name, $uri, $json_encoder) = @_;
57 0   0       $json_encoder ||= $default_json;
58 0           my $self = bless { name => $name, uri => $uri, json_encoder => $json_encoder } => $class;
59 0 0         if (my $userinfo = $self->uri->userinfo) {
60 0           my $auth = encode_base64($userinfo, '');
61 0           $self->{http_auth} = "Basic $auth";
62             }
63 0           return $self;
64             }
65              
66             sub name {
67 0     0 1   $_[0]->{name};
68             }
69              
70             sub uri {
71 0     0 1   $_[0]->{uri};
72             }
73              
74             sub json_encoder {
75 0     0 1   my ($self, $encoder) = @_;
76 0 0         if ($encoder) {
77 0           $self->{json_encoder} = $encoder;
78             } else {
79 0           $self->{json_encoder};
80             }
81             }
82              
83             sub json {
84 0     0 0   my ( $self, $target ) = @_;
85 0 0         ref($target) ? $self->json_encoder->encode($target) : $target;
86             }
87              
88             sub compact {
89 0     0 1   my ( $self, $options ) = @_;
90 0           my ( $cv, $cb ) = cvcb( $options, 202, $self->json_encoder );
91 0           http_request(
92             POST => ( $self->uri . "_compact" ),
93             headers => $self->_build_headers($options),
94             $cb
95             );
96 0           $cv;
97             }
98              
99             sub create {
100 0     0 1   my ( $self, $options ) = @_;
101 0           my ( $cv, $cb ) = cvcb( $options, 201, $self->json_encoder );
102 0           http_request(
103             PUT => $self->uri,
104             headers => $self->_build_headers($options),
105             $cb
106             );
107 0           $cv;
108             }
109              
110             sub drop {
111 0     0 1   my ( $self, $options ) = @_;
112 0           my ( $cv, $cb ) = cvcb( $options, undef, $self->json_encoder );
113 0           http_request(
114             DELETE => $self->uri,
115             headers => $self->_build_headers($options),
116             $cb
117             );
118 0           $cv;
119             }
120              
121             sub info {
122 0     0 1   my ( $self, $options ) = @_;
123 0           my ( $cv, $cb ) = cvcb( $options, undef, $self->json_encoder );
124 0           http_request(
125             GET => $self->uri,
126             headers => $self->_build_headers($options),
127             $cb
128             );
129 0           $cv;
130             }
131              
132             sub all_docs {
133 0     0 1   my ( $self, $options ) = @_;
134 0           my ( $cv, $cb ) = cvcb( $options, undef, $self->json_encoder );
135 0           http_request(
136             GET => $self->uri . '_all_docs' . $query->($options),
137             headers => $self->_build_headers($options),
138             $cb
139             );
140 0           $cv;
141             }
142              
143             sub all_docs_by_seq {
144 0     0 1   my ( $self, $options ) = @_;
145 0           my ( $cv, $cb ) = cvcb( $options, undef, $self->json_encoder );
146 0           http_request(
147             GET => $self->uri . '_all_docs_by_seq' . $query->($options),
148             headers => $self->_build_headers($options),
149             $cb
150             );
151 0           $cv;
152             }
153              
154             sub open_doc {
155 0     0 1   my ( $self, $doc_id, $options ) = @_;
156 0 0         if ( not defined $doc_id ) {
157 0           AnyEvent::CouchDB::Exception::UndefinedDocument->throw(
158             "An undefined id was passed to open_doc()."
159             );
160             }
161 0           my ( $cv, $cb ) = cvcb( $options, undef, $self->json_encoder );
162 0           my $id = uri_escape_utf8($doc_id);
163 0 0         if ( $id =~ qr{^_design%2F} ) {
164 0           $id =~ s{%2F}{/}g;
165             }
166             http_request(
167 0           GET => $self->uri . $id . $query->($options),
168             headers => $self->_build_headers($options),
169             $cb
170             );
171 0           $cv;
172             }
173              
174             sub open_docs {
175 0     0 1   my ( $self, $doc_ids, $options ) = @_;
176 0           my ( $cv, $cb ) = cvcb( $options, undef, $self->json_encoder );
177 0   0       $options ||= {};
178 0           $options->{'include_docs'} = 'true';
179 0           http_request(
180             POST => $self->uri . '_all_docs' . $query->($options),
181             headers => $self->_build_headers($options),
182             body => $self->json( { "keys" => $doc_ids } ),
183             $cb
184             );
185 0           $cv;
186             }
187              
188             sub save_doc {
189 0     0 1   my ( $self, $doc, $options ) = @_;
190              
191             # create attachment stubs for new inlined attachments
192             my $_attachments = sub {
193 0     0     my ( $doc ) = @_;
194 0           my $_a = $doc->{_attachments};
195 0 0         return unless defined $_a;
196 0           my $revpos = $doc->{_rev};
197 0           $revpos =~ s/-.*$//;
198 0           for my $key (keys %$_a) {
199 0 0         if ( exists($_a->{$key}{data}) ) {
200 0           my $file = $_a->{$key};
201 0           $file->{length} = length(decode_base64($file->{data}));
202 0           $file->{revpos} = $revpos;
203 0           $file->{stub} = JSON::true();
204 0           delete $file->{data};
205             }
206             }
207 0           };
208              
209 0 0         if ( $options->{success} ) {
210 0           my $orig = $options->{success};
211             $options->{success} = sub {
212 0     0     my ($resp) = @_;
213 0           $orig->($resp);
214 0           $doc->{_id} = $resp->{id};
215 0           $doc->{_rev} = $resp->{rev};
216 0           $_attachments->($doc);
217 0           };
218             }
219             else {
220             $options->{success} = sub {
221 0     0     my ($resp) = @_;
222 0           $doc->{_id} = $resp->{id};
223 0           $doc->{_rev} = $resp->{rev};
224 0           $_attachments->($doc);
225 0           };
226             }
227 0           my ( $cv, $cb ) = cvcb( $options, 201, $self->json_encoder );
228 0           my ( $method, $uri );
229 0 0         if ( not defined $doc->{_id} ) {
230 0           $method = 'POST';
231 0           $uri = $self->uri;
232             }
233             else {
234 0           $method = 'PUT';
235 0           $uri = $self->uri . uri_escape_utf8( $doc->{_id} );
236             }
237 0           http_request(
238             $method => $uri . $query->($options),
239             headers => $self->_build_headers($options),
240             body => $self->json($doc),
241             $cb
242             );
243 0           $cv;
244             }
245              
246             sub remove_doc {
247 0     0 1   my ( $self, $doc, $options ) = @_;
248 0 0         die("Document is missing _id!") unless ( defined $doc->{_id} );
249 0           my ( $cv, $cb ) = cvcb( $options, undef, $self->json_encoder );
250 0           http_request(
251             DELETE => $self->uri
252             . uri_escape_utf8( $doc->{_id} )
253             . $query->( { rev => $doc->{_rev} } ),
254             headers => $self->_build_headers($options),
255             $cb
256             );
257 0           $cv;
258             }
259              
260             sub attach {
261 0     0 1   my ( $self, $doc, $attachment, $options ) = @_;
262 0           my $body < io( $options->{src} );
263 0           my $length = length($body);
264 0   0       $options->{type} ||= 'text/plain';
265 0 0         if ( $options->{success} ) {
266 0           my $orig = $options->{success};
267             $options->{success} = sub {
268 0     0     my ($resp) = @_;
269 0           $orig->($resp);
270 0           $doc->{_id} = $resp->{id};
271 0           $doc->{_rev} = $resp->{rev};
272 0   0       $doc->{_attachments} ||= {};
273 0           $doc->{_attachments}->{$attachment} = {
274             'content_type' => $options->{type},
275             'length' => $length,
276             'stub' => JSON::true,
277             };
278 0           };
279             }
280             else {
281             $options->{success} = sub {
282 0     0     my ($resp) = @_;
283 0           $doc->{_id} = $resp->{id};
284 0           $doc->{_rev} = $resp->{rev};
285 0   0       $doc->{_attachments} ||= {};
286 0           $doc->{_attachments}->{$attachment} = {
287             'content_type' => $options->{type},
288             'length' => $length,
289             'stub' => JSON::true,
290             };
291 0           };
292             }
293 0           my ( $cv, $cb ) = cvcb( $options, 201, $self->json_encoder );
294 0           http_request(
295             PUT => $self->uri
296             . uri_escape_utf8( $doc->{_id} ) . "/"
297             . uri_escape_utf8($attachment)
298             . $query->( { rev => $doc->{_rev} } ),
299             headers => $self->_build_headers($options),
300             body => $body,
301             $cb
302             );
303 0           $cv;
304             }
305              
306             sub open_attachment {
307 0     0 1   my ( $self, $doc, $attachment, $options ) = @_;
308 0           my $cv = AnyEvent->condvar;
309              
310             # passthrough handler without json encoding
311             my $success = sub {
312 0 0   0     $options->{success}->(@_) if ($options->{success});
313 0           $cv->send(@_);
314 0           };
315              
316             # error handler that croaks with http headers
317             my $error = sub {
318 0     0     my $headers = shift;
319 0 0         $options->{error}->(@_) if ($options->{error});
320 0           $cv->croak(encode_json $headers);
321 0           };
322              
323             my $cb = sub {
324 0     0     my ($body, $headers) = @_;
325 0 0 0       if ($headers->{Status} >= 200 and $headers->{Status} < 400) {
326 0           $success->(@_);
327             } else {
328 0           $error->($headers);
329             }
330 0           };
331              
332 0           http_request(
333             GET => $self->uri
334             . uri_escape_utf8( $doc->{_id} ) . "/"
335             . uri_escape_utf8($attachment),
336             headers => $self->_build_headers($options),
337             $cb
338             );
339 0           $cv;
340             }
341              
342             sub detach {
343 0     0 1   my ( $self, $doc, $attachment, $options ) = @_;
344 0 0         if ( $options->{success} ) {
345 0           my $orig = $options->{success};
346             $options->{success} = sub {
347 0     0     my ($resp) = @_;
348 0           $orig->($resp);
349 0           $doc->{_id} = $resp->{id};
350 0           $doc->{_rev} = $resp->{rev};
351 0           delete $doc->{_attachments}->{$attachment};
352 0           };
353             }
354             else {
355             $options->{success} = sub {
356 0     0     my ($resp) = @_;
357 0           $doc->{_id} = $resp->{id};
358 0           $doc->{_rev} = $resp->{rev};
359 0           delete $doc->{_attachments}->{$attachment};
360 0           };
361             }
362 0           my ( $cv, $cb ) = cvcb( $options, undef, $self->json_encoder );
363 0           http_request(
364             DELETE => $self->uri
365             . uri_escape_utf8( $doc->{_id} ) . "/"
366             . uri_escape_utf8($attachment)
367             . $query->( { rev => $doc->{_rev} } ),
368             headers => $self->_build_headers($options),
369             $cb
370             );
371 0           $cv;
372             }
373              
374             sub bulk_docs {
375 0     0 1   my ( $self, $docs, $options ) = @_;
376 0           my ( $cv, $cb ) = cvcb( $options, undef, $self->json_encoder );
377              
378 0           my %props = (); ## _bulk_docs properties go to the request body
379 0           foreach my $property (qw(all_or_nothing new_edits)) {
380 0 0         if (my $value = delete $options->{$property}) {
381             ## convert the respective value to the JSON boolean type
382 0 0         $props{$property} = $value eq 'false' ? JSON::false() : JSON::true();
383             }
384             }
385              
386             http_request(
387 0           POST => $self->uri . '_bulk_docs',
388             headers => $self->_build_headers($options),
389             body => $self->json( { %props, docs => $docs } ),
390             $cb
391             );
392 0           $cv;
393             }
394              
395             sub query {
396 0     0 1   my ( $self, $map_fun, $reduce_fun, $language, $options ) = @_;
397 0           my ( $cv, $cb ) = cvcb( $options, undef, $self->json_encoder );
398 0 0 0       $language ||= ( ref($map_fun) eq 'CODE' ) ? 'text/perl' : 'javascript';
399 0           my $body = {
400             language => $language,
401             map => $code_to_string->($map_fun),
402             };
403 0 0         if ($reduce_fun) {
404 0           $body->{reduce} = $code_to_string->($reduce_fun);
405             }
406             http_request(
407 0           POST => $self->uri . '_temp_view' . $query->($options),
408             headers => $self->_build_headers($options),
409             body => $self->json($body),
410             $cb
411             );
412 0           $cv;
413             }
414              
415             sub view {
416 0     0 1   my ( $self, $name, $options ) = @_;
417 0           my ( $cv, $cb ) = cvcb( $options, undef, $self->json_encoder );
418 0           my ( $dname, $vname ) = split( '/', $name );
419 0           my $uri = $self->uri . "_design/" . $dname . "/_view/" . $vname;
420 0 0         if ( $options->{keys} ) {
421 0           my $body = { keys => $options->{keys} };
422 0           my $opts = { %$options };
423 0           delete $opts->{keys};
424 0           http_request(
425             POST => $uri . $query->($opts),
426             headers => $self->_build_headers($options),
427             body => $self->json($body),
428             $cb
429             );
430             }
431             else {
432 0           my $headers = $self->_build_headers($options);
433 0           http_request(
434             GET => $uri . $query->($options),
435             headers => $headers,
436             $cb
437             );
438             }
439 0           $cv;
440             }
441              
442             sub head {
443 0     0 1   my ( $self, $path, $options ) = @_;
444 0           my ( $cv, undef ) = cvcb( $options, undef, $self->json_encoder );
445 0           my $headers = $self->_build_headers($options);
446 0           my $uri = $self->uri . "$path" . $query->($options);
447             http_request(
448             HEAD => $uri,
449             headers => $headers,
450 0     0     sub { $cv->send( $_[1] ); }
451 0           );
452 0           $cv;
453             }
454              
455             sub get {
456 0     0 1   my ( $self, $path, $options ) = @_;
457 0           my ( $cv, $cb ) = cvcb( $options, undef, $self->json_encoder );
458 0           my $headers = $self->_build_headers($options);
459 0           my $uri = $self->uri . "$path" . $query->($options);
460 0           http_request(
461             GET => $uri,
462             headers => $headers,
463             $cb
464             );
465 0           $cv;
466             }
467              
468             sub post {
469 0     0 1   my ( $self, $path, $options ) = @_;
470 0           my ( $cv, $cb ) = cvcb( $options, undef, $self->json_encoder );
471 0           my $headers = $self->_build_headers($options);
472 0           my $uri = $self->uri . "$path";
473 0           http_request(
474             POST => $uri,
475             headers => $headers,
476             body => $query->($options),
477             $cb
478             );
479 0           $cv;
480             }
481              
482             sub delete {
483 0     0 1   my ( $self, $path, $options ) = @_;
484 0           my ( $cv, $cb ) = cvcb( $options, undef, $self->json_encoder );
485 0           my $headers = $self->_build_headers($options);
486 0           my $uri = $self->uri . "$path" . $query->($options);
487 0           http_request(
488             DELETE => $uri,
489             headers => $headers,
490             $cb
491             );
492 0           $cv;
493             }
494              
495             sub put {
496 0     0 1   my ( $self, $path, $options ) = @_;
497 0           my ( $cv, $cb ) = cvcb( $options, undef, $self->json_encoder );
498 0           my $headers = $self->_build_headers($options);
499 0           my $uri = $self->uri . "$path";
500 0           http_request(
501             PUT => $uri,
502             headers => $headers,
503             body => $query->($options),
504             $cb
505             );
506 0           $cv;
507             }
508              
509              
510             __END__