File Coverage

blib/lib/Mojo/CouchDB.pm
Criterion Covered Total %
statement 120 157 76.4
branch 38 64 59.3
condition 8 20 40.0
subroutine 30 37 81.0
pod 14 14 100.0
total 210 292 71.9


line stmt bran cond sub pod time code
1             package Mojo::CouchDB;
2              
3 2     2   1285213 use Mojo::Base -base;
  2         15  
  2         13  
4 2     2   345 use Mojo::URL;
  2         4  
  2         16  
5 2     2   127 use Mojo::UserAgent;
  2         4  
  2         14  
6 2     2   90 use Mojo::IOLoop;
  2         4  
  2         21  
7 2     2   125 use Mojo::JSON qw(encode_json);
  2         7  
  2         108  
8              
9 2     2   14 use Carp qw(croak carp);
  2         4  
  2         98  
10 2     2   1201 use URI;
  2         10828  
  2         66  
11 2     2   13 use MIME::Base64;
  2         5  
  2         137  
12 2     2   13 use Scalar::Util qw(reftype);
  2         4  
  2         92  
13 2     2   15 use Storable qw(dclone);
  2         4  
  2         4960  
14              
15             our $VERSION = '0.6';
16              
17             has 'url';
18             has 'auth';
19             has ua => sub { Mojo::UserAgent->new };
20              
21             sub all_docs {
22 2     2 1 65 my $self = shift;
23 2         8 my $query = $self->_to_query(shift);
24 0         0 return $self->_call('_all_docs' . $query, 'get')->result->json;
25             }
26              
27             sub all_docs_p {
28 2     2 1 64 my $self = shift;
29 2         7 my $query = $self->_to_query(shift);
30              
31             return $self->_call_p('_all_docs' . $query, 'get_p')
32 0     0   0 ->then(sub { return shift->res->json });
  0         0  
33             }
34              
35             sub create_db {
36 2     2 1 5070 return shift->_call('', 'put');
37             }
38              
39             sub find {
40 2     2 1 65 my $self = shift;
41 2         5 my $sc = shift;
42 2         6 return $self->_find($sc)->_call('_find', 'post', $sc);
43             }
44              
45             sub find_p {
46 2     2 1 74 my $self = shift;
47 2         4 my $sc = shift;
48             return $self->_find($sc)->_call_p('_find', 'post_p', $sc)
49 2     0   8 ->then(sub { return shift->res->json });
  0         0  
50             }
51              
52             sub get {
53 5     5 1 152 my $self = shift;
54 5         12 my $id = shift;
55              
56 5         32 $id = $$id while (reftype($id));
57 4         20 return $self->_get($id)->_call("/$id", 'get');
58             }
59              
60             sub get_p {
61 0     0 1 0 my $self = shift;
62 0         0 my $id = shift;
63              
64 0         0 $id = $$id while (reftype($id));
65 0         0 return $self->_get($id)->_call_p("/$id", 'get');
66             }
67              
68             sub index {
69 2     2 1 65 my $self = shift;
70 2         3 my $idx = shift;
71              
72 2         9 return $self->_index($idx)->_call('_index', 'post', $idx);
73             }
74              
75             sub index_p {
76 2     2 1 63 my $self = shift;
77 2         3 my $idx = shift;
78              
79 2         8 return $self->_index($idx)->_call_p('_index', 'post_p', $idx);
80             }
81              
82             sub new {
83 2     2 1 3120 my $self = shift->SUPER::new;
84 2         15 my $str = shift;
85 2         16 my $username = shift;
86 2         6 my $password = shift;
87              
88 2 50       9 return $self unless $str;
89              
90 2 50       11 chop $str if substr($str, -1) eq '/';
91              
92 2         17 my $url = Mojo::URL->new($str);
93 2 50       430 croak qq{Invalid CouchDB URI string "$str"} unless $url->protocol =~ /^http(?:s)?$/;
94 2 50       30 croak qq{No database specified in connection string}
95             unless exists $url->path->parts->[0];
96 2 50 33     222 carp qq{No username or password provided for CouchDB} unless $username and $password;
97              
98 2 50 33     15 if ($username and $password) {
99 2         29 chomp($self->{auth} = 'Basic ' . encode_base64("$username:$password"));
100             }
101              
102 2         7 $self->{url} = $url;
103              
104 2         6 return $self;
105             }
106              
107             sub save {
108 4     4 1 79 my $self = shift;
109 4         6 my $doc = shift;
110 4         15 my $res = $self->_save($doc)->_call('', 'post', $doc);
111              
112 2         166 my $dc = dclone $doc;
113 2         11 $dc->{_id} = $res->{_id};
114 2         7 $dc->{_rev} = $res->{_rev};
115              
116 2         13 return $dc;
117             }
118              
119             sub save_p {
120 2     2 1 64 my $self = shift;
121 2         4 my $doc = shift;
122             return $self->_save($doc)->_call_p('', 'post_p', $doc)->then(sub {
123 0     0   0 my $res = shift;
124              
125 0         0 my $dc = dclone $doc;
126 0         0 $dc->{_id} = $res->{_id};
127 0         0 $dc->{_rev} = $res->{_rev};
128              
129 0         0 return $doc;
130 2         5 });
131             }
132              
133             sub save_many {
134 2     2 1 67 my $self = shift;
135 2         4 my $docs = shift;
136 2         7 return $self->_save_many($docs)->_call('/bulk_docs', 'post', {docs => $docs});
137             }
138              
139             sub save_many_p {
140 2     2 1 64 my $self = shift;
141 2         5 my $docs = shift;
142              
143 2         7 return $self->_save_many($docs)->_call_p('/bulk_docs', 'post_p', {docs => $docs});
144             }
145              
146             sub _call {
147 7     7   16 my $self = shift;
148 7         11 my $loc = shift;
149 7         14 my $method = shift;
150 7         12 my $body = shift;
151              
152 7 100 66     57 my $url = $loc && $loc ne '' ? $self->url->to_string . "$loc" : $self->url->to_string;
153              
154 7         2263 my $headers = {Authorization => $self->auth};
155              
156 7 100       95 my $r
157             = ($body
158             ? $self->ua->$method($url, $headers, 'json', $body)
159             : $self->ua->$method($url, $headers))->result;
160              
161             croak 'CouchDB encountered an error: ' . $r->json->{error}
162 7 50 66     136426 if $r->json and exists($r->json->{error});
163 7 100       2527 croak 'CouchDB encountered an error: ' . $r->code . ' ' . encode_json($r->json)
164             unless $r->is_success;
165              
166 6   100     114 return $r->json || {};
167             }
168              
169             sub _call_p {
170 0     0   0 my $self = shift;
171 0         0 my $loc = shift;
172 0         0 my $method = shift;
173 0         0 my $body = shift;
174              
175 0 0 0     0 my $url = $loc && $loc ne '' ? $self->url->to_string . "$loc" : $self->url->to_string;
176              
177 0         0 my $headers = {Authorization => $self->auth};
178              
179 0 0       0 if ($body) {
180             return $self->ua->$method($url, $headers, 'json', $body)->then(sub {
181 0     0   0 my $r = shift;
182              
183             croak 'CouchDB encountered an error: ' . $r->res->json->{error}
184 0 0       0 if (exists $r->res->json->{error});
185              
186 0         0 return $r->res->json;
187 0         0 });
188             }
189              
190             return $self->ua->$method($url, $headers)->then(sub {
191 0     0   0 my $r = shift;
192              
193             croak 'CouchDB encountered an error: ' . $r->res->json->{error}
194 0 0       0 if (exists $r->res->json->{error});
195 0 0       0 croak 'CouchDB encountered an error: '
196             . $r->res->code . ' '
197             . encode_json($r->res->json)
198             if (!$r->is_success);
199              
200 0         0 return $r->res->json;
201 0         0 });
202             }
203              
204             sub _find {
205 4     4   8 my $self = shift;
206 4         8 my $sc = shift;
207              
208 4 100       28 croak qq{Invalid type supplied for search criteria, expected hashref got: undef }
209             unless $sc;
210 2 50       25 croak qq{Invalid type supplied for search criteria, expected hashref got: scalar }
211             unless reftype $sc;
212 0 0       0 croak qq{Invalid type supplied for search criteria, expected hashref got: }
213             . reftype $sc
214             unless reftype($sc) eq 'HASH';
215              
216 0         0 return $self;
217             }
218              
219             sub _get {
220 4     4   7 my $self = shift;
221 4         7 my $id = shift;
222              
223 4 100       16 croak qq{Invalid type supplied for id, expected scalar got: undef} unless $id;
224              
225 3         11 return $self;
226             }
227              
228             sub _index {
229 4     4   7 my $self = shift;
230 4         7 my $idx = shift;
231              
232 4 100       28 croak qq{Invalid type supplied for index, expected hashref got: undef } unless $idx;
233 2 100       21 croak qq{Invalid type supplied for index, expected hashref got: scalar }
234             unless reftype $idx;
235 1 50       19 croak qq{Invalid type supplied for index, expected hashref got: } . reftype $idx
236             unless reftype($idx) eq 'HASH';
237              
238 0         0 return $self;
239             }
240              
241             sub _save {
242 6     6   14 my $self = shift;
243 6         9 my $doc = shift;
244              
245 6 100       36 croak qq{No save argument specified, expected hashref got: undef } unless $doc;
246 4 100       28 croak qq{Invalid type supplied for document, expected hashref got: scalar }
247             unless reftype $doc;
248 3 100       24 croak qq{Invalid type supplied for document, expected hashref got: } . (reftype $doc)
249             unless reftype($doc) eq 'HASH';
250 2 50       5 croak qq{Cannot call save without a document} unless (defined $doc);
251              
252 2         8 return $self;
253             }
254              
255             sub _save_many {
256 4     4   7 my $self = shift;
257 4         8 my $docs = shift;
258              
259 4 100       28 croak qq{Cannot save many without a documents} unless defined $docs;
260 2 100       20 croak
261             qq{Invalid type supplied for documents, expected arrayref of hashref's got: scalar }
262             unless reftype $docs;
263 1 50       15 croak qq{Invalid type supplied for documents, expected arrayref of hashref's got: }
264             . (reftype $docs)
265             unless (reftype($docs) eq 'ARRAY');
266              
267 0         0 return $self;
268             }
269              
270             sub _to_query {
271 4     4   10 my $self = shift;
272 4         7 my $query = shift;
273              
274 4 100       26 croak qq{Invalid type supplied for query, expected hashref got undef} unless $query;
275 2 50       27 croak qq{Invalid type supplied for query, expected hashref got scalar}
276             unless reftype $query;
277 0 0 0       croak qq{Invalid type supplied for query, expected hashref got: } . reftype($query)
278             unless $query && reftype($query) eq 'HASH';
279              
280 0           my $t_uri = URI->new('', 'http');
281 0           $t_uri->query_form(%$query);
282              
283 0           return $t_uri->query;
284             }
285              
286             1;
287              
288             =encoding utf8
289              
290             =head1 NAME
291              
292             Mojo::CouchDB
293              
294             =head1 SYNOPSIS
295              
296             use Mojo::CouchDB;
297              
298             # Create a CouchDB instance
299             my $couch = Mojo::CouchDB->new('http://localhost:6984/books', 'username', 'password');
300              
301             # Make a document
302             my $book = {
303             title => 'Nineteen Eighty Four',
304             author => 'George Orwell'
305             };
306              
307             # Save your document to the database
308             $book = $couch->save($book);
309              
310             # If _id is assigned to a hashref, save will update rather than create
311             say $book->{_id}; # Assigned when saving or getting
312             $book->{title} = 'Dune';
313             $book->{author} = 'Frank Herbert'
314              
315             # Re-save to update the document
316             $book = $couch->save($book);
317              
318             # Get the document as a hashref
319             my $dune = $couch->get($book->{_id});
320              
321             # You can also save many documents at a time
322             my $books = $couch->save_many([{title => 'book', author => 'John'}, { title => 'foo', author => 'bar' }])->{docs};
323              
324             =head2 all_docs
325              
326             $couch->all_docs({ limit => 10, skip => 5});
327              
328             Retrieves a list of all of the documents in the database. This is packaged as a hashref with
329             C: the offset of the query, C: the documents in the page, and C: the number of rows returned in the dataset.
330              
331             Optionally, can take a hashref of query parameters that correspond with the CouchDB L.
332              
333             =head2 all_docs_p
334              
335             $couch->all_docs_p({ limit => 10, skip => 5 });
336              
337             See L<\"all_docs">, except returns the result in a L.
338            
339             =head2 create_db
340              
341             $couch->create_db
342              
343             Create the database, returns C<1> if succeeds or if it already exsits, else returns C.
344              
345             =head2 find
346              
347             $couch->find($search_criteria);
348              
349             Searches for documents based on the search criteria specified. The search criteria hashref provided for searching must follow the CouchDB L<_find specification|https://docs.couchdb.org/en/stable/api/database/find.html#selector-syntax>.
350              
351             Returns a hashref with two fields: C and C. C contains an arrayref of found documents, while
352             C contains a hashref of various statistics about the query you ran to find the documents.
353              
354             =head2 find_p
355              
356             $couch->find_p($search_criteria);
357              
358             See L<\"find">, except returns the result asynchronously in a L.
359              
360             =head2 get
361              
362             $couch->get($id);
363              
364             Finds a document by a given id. Dies if it can't find the document. Returns the document in hashref form.
365              
366             =head2 get_p
367              
368             $couch->get_p($id);
369              
370             See L<\"get">, except returns the result asynchronously in a L.
371              
372             =head2 index
373              
374             $couch->index($idx);
375              
376             Creates an index, where C<$idx> is a hashref following the CouchDB L. Returns the result of the index creation.
377              
378             =head2 index_p
379              
380             $couch->index_p($idx);
381              
382             See L<\"index">, except returns the result asynchronously in a L.
383              
384             =head2 new
385              
386             my $url = 'https://127.0.0.1:5984/my_database';
387             my $couch = Mojo::CouchDB->new($url, $username, $password);
388              
389             Creates an instance of L<\"Mojo::CouchDB">. The URL specified must include the protocol either C or C as well as the port your CouchDB instance is using, and the name of the database you want to manipulate.
390              
391             =head2 save
392              
393             $couch->save($document);
394              
395             Saves a document (hashref) to the database. If the C<_id> field is provided, it will update if it already exists. If you provide both the C<_id> and C<_rev> field, that specific revision will be updated. Returns a hashref that corresponds to the CouchDB C specification.
396              
397             =head2 save_p
398              
399             $couch->save_p($document);
400              
401             Does the same as L<\"save"> but instead returns the result asynchronously in a L.
402              
403             =head2 save_many
404              
405             $couch->save_many($documents);
406              
407             Saves an arrayref of documents (hashrefs) to the database. Each document follows the same rules as L<\"save">. Returns an arrayref of the documents you saved with the C<_id> and C<_rev> fields filled.
408              
409             =head2 save_many_p
410              
411             $couch->save_many_p($documents);
412              
413             See L<\"save_many">, except returns the result asynchronously in a L.
414              
415             =head1 API
416              
417             =over 2
418              
419             =item * L
420              
421             =back
422              
423             =head1 AUTHOR
424              
425             Rawley Fowler, C.
426              
427             =head1 CREDITS
428              
429             =over 2
430              
431             =back
432              
433             =head1 LICENSE
434              
435             Copyright (C) 2023, Rawley Fowler and contributors.
436              
437             This program is free software, you can redistribute it and/or modify it under the terms of the Artistic License version 2.0.
438              
439             =head1 SEE ALSO
440              
441             L.
442              
443             =cut