File Coverage

blib/lib/Catmandu/Store/MongoDB.pm
Criterion Covered Total %
statement 20 55 36.3
branch 2 8 25.0
condition 4 14 28.5
subroutine 6 9 66.6
pod 1 2 50.0
total 33 88 37.5


line stmt bran cond sub pod time code
1             package Catmandu::Store::MongoDB;
2              
3 3     3   59583 use Catmandu::Sane;
  3         463293  
  3         22  
4              
5             our $VERSION = '0.0803';
6              
7 3     3   820 use Moo;
  3         6  
  3         15  
8 3     3   2366 use Catmandu::Store::MongoDB::Bag;
  3         11  
  3         88  
9 3     3   1284 use MongoDB;
  3         3685351  
  3         105  
10 3     3   25 use namespace::clean;
  3         7  
  3         17  
11              
12             with 'Catmandu::Store';
13             with 'Catmandu::Transactional';
14              
15             has client => (is => 'lazy');
16             has database_name => (is => 'ro', required => 1);
17             has database => (is => 'lazy', handles => [qw(drop)]);
18             has session =>
19             (is => 'rw', predicate => 1, clearer => 1, writer => 'set_session');
20              
21             with 'Catmandu::Droppable';
22              
23             sub _build_client {
24 0     0   0 my $self = shift;
25 0         0 my $args = delete $self->{_args};
26 0   0     0 my $host = $self->{_args}->{host} // 'mongodb://localhost:27017';
27 0         0 $self->log->debug("Build MongoClient for $host");
28 0         0 my $client = MongoDB::MongoClient->new($args);
29 0         0 return $client;
30             }
31              
32             sub _build_database {
33 0     0   0 my $self = shift;
34 0         0 my $database_name = $self->database_name;
35 0         0 $self->log->debug("Build or get database $database_name");
36 0         0 my $database = $self->client->get_database($database_name);
37 0         0 return $database;
38             }
39              
40             sub BUILD {
41 1     1 0 27786 my ($self, $args) = @_;
42              
43 1         4 $self->{_args} = {};
44 1         4 for my $key (keys %$args) {
45             next
46 2 100 66     22 if $key eq 'client'
      66        
47             || $key eq 'database_name'
48             || $key eq 'database';
49 1         4 $self->{_args}{$key} = $args->{$key};
50             }
51             }
52              
53             sub transaction {
54 0     0 1   my ($self, $sub) = @_;
55              
56 0 0         if ($self->has_session) {
57 0           return $sub->();
58             }
59              
60 0           my $session = $self->client->start_session;
61 0           my @res;
62              
63             eval {
64 0           $self->set_session($session);
65 0           $session->start_transaction;
66              
67 0           @res = $sub->();
68              
69             COMMIT: {
70 0           eval {
71 0           $session->commit_transaction;
72 0           1;
73 0   0       } // do {
74 0           my $err = $@;
75 0 0         if ($err->has_error_label("UnknownTransactionCommitResult")) {
76 0           redo COMMIT;
77             }
78             else {
79 0           die $err;
80             }
81             };
82             }
83              
84 0           $self->clear_session;
85              
86 0           1;
87 0   0       } // do {
88 0           my $err = $@;
89 0           $session->abort_transaction;
90 0           $self->clear_session;
91 0           die $err;
92             };
93              
94 0 0         wantarray ? @res : $res[0];
95             }
96              
97             1;
98              
99             __END__
100              
101             =pod
102              
103             =head1 NAME
104              
105             Catmandu::Store::MongoDB - A searchable store backed by MongoDB
106              
107             =head1 SYNOPSIS
108              
109             # On the command line
110             $ catmandu import -v JSON --multiline 1 to MongoDB --database_name bibliography --bag books < books.json
111             $ catmandu export MongoDB --database_name bibliography --bag books to YAML
112             $ catmandu count MongoDB --database_name bibliography --bag books --query '{"PublicationYear": "1937"}'
113              
114             # In perl
115             use Catmandu::Store::MongoDB;
116              
117             my $store = Catmandu::Store::MongoDB->new(database_name => 'test');
118              
119             my $obj1 = $store->bag->add({ name => 'Patrick' });
120              
121             printf "obj1 stored as %s\n" , $obj1->{_id};
122              
123             # Force an id in the store
124             my $obj2 = $store->bag->add({ _id => 'test123' , name => 'Nicolas' });
125              
126             my $obj3 = $store->bag->get('test123');
127              
128             $store->bag->delete('test123');
129              
130             $store->bag->delete_all;
131              
132             # All bags are iterators
133             $store->bag->each(sub { ... });
134             $store->bag->take(10)->each(sub { ... });
135              
136             # Search
137             my $hits = $store->bag->search(query => '{"name":"Patrick"}');
138             my $hits = $store->bag->search(query => '{"name":"Patrick"}' , sort => { age => -1} );
139             my $hits = $store->bag->search(query => {name => "Patrick"} , start => 0 , limit => 100);
140             my $hits = $store->bag->search(query => {name => "Patrick"} , fields => {_id => 0, name => 1});
141              
142             my $next_page = $hits->next_page;
143             my $hits = $store->bag->search(query => '{"name":"Patrick"}' , page => $next_page);
144              
145             my $iterator = $store->bag->searcher(query => {name => "Patrick"});
146             my $iterator = $store->bag->searcher(query => {name => "Patrick"}, fields => {_id => 0, name => 1});
147              
148             # Catmandu::Store::MongoDB supports CQL...
149             my $hits = $store->bag->search(cql_query => 'name any "Patrick"');
150              
151             =head1 DESCRIPTION
152              
153             A Catmandu::Store::MongoDB is a Perl package that can store data into
154             L<MongoDB> databases. The database as a whole is called a 'store'.
155             Databases also have compartments (e.g. tables) called Catmandu::Bag-s.
156              
157             =head1 METHODS
158              
159             =head2 new(database_name => $name, %connection_opts)
160              
161             =head2 new(database_name => $name , bags => { data => { cql_mapping => $cql_mapping } })
162              
163             Create a new Catmandu::Store::MongoDB store with name $name. Optionally
164             provide connection parameters (see L<MongoDB::MongoClient> for possible
165             options).
166              
167             The store supports CQL searches when a cql_mapping is provided. This hash
168             contains a translation of CQL fields into MongoDB searchable fields.
169              
170             # Example mapping
171             $cql_mapping = {
172             indexes => {
173             title => {
174             op => {
175             'any' => 1 ,
176             'all' => 1 ,
177             '=' => 1 ,
178             '<>' => 1 ,
179             'exact' => {field => [qw(mytitle.exact myalttitle.exact)]}
180             } ,
181             sort => 1,
182             field => 'mytitle',
183             cb => ['Biblio::Search', 'normalize_title']
184             }
185             }
186             }
187              
188             The CQL mapping above will support for the 'title' field the CQL operators:
189             any, all, =, <> and exact.
190              
191             The 'title' field will be mapped into the MongoDB field 'mytitle',
192             except for the 'exact' operator. In case of 'exact' both the
193             'mytitle.exact' and 'myalttitle.exact' fields will be searched.
194              
195             The CQL mapping allows for sorting on the 'title' field. If, for instance, we
196             would like to use a special MongoDB field for sorting we could have written
197             "sort => { field => 'mytitle.sort' }".
198              
199             The CQL has an optional callback field 'cb' which contains a reference to subroutines
200             to rewrite or augment the search query. In this case, in the Biblio::Search package
201             contains a normalize_title subroutine which returns a string or an ARRAY of string
202             with augmented title(s). E.g.
203              
204             package Biblio::Search;
205              
206             sub normalize_title {
207             my ($self,$title) = @_;
208             # delete all bad characters
209             my $new_title =~ s{[^A-Z0-9]+}{}g;
210             $new_title;
211             }
212              
213             1;
214              
215             =head2 bag($name)
216              
217             Create or retieve a bag with name $name. Returns a L<Catmandu::Bag>.
218              
219             =head2 client
220              
221             Return the L<MongoDB::MongoClient> instance.
222              
223             =head2 database
224              
225             Return a L<MongoDB::Database> instance.
226              
227             =head2 drop
228              
229             Delete the store and all it's bags.
230              
231             =head2 transaction(\&sub)
232              
233             Execute C<$sub> within a transaction. See L<Catmandu::Transactional>.
234              
235             Note that only MongoDB databases with feature compatibility >= 4.0 and in a
236             replica set have support for transactions. See
237             L<https://docs.mongodb.com/manual/reference/command/setFeatureCompatibilityVersion/#view-fcv>
238             and
239             L<https://docs.mongodb.com/manual/tutorial/convert-standalone-to-replica-set/>
240             for more info.
241              
242             =head1 Search
243              
244             Search the database: see L<Catmandu::Searchable> and L<Catmandu::CQLSearchable>. This module supports an additional search parameter:
245              
246             - fields => { <field> => <0|1> } : limit fields to return from a query (see L<MongoDB Tutorial|https://docs.mongodb.org/manual/tutorial/project-fields-from-query-results/>)
247              
248             =head1 SEE ALSO
249              
250             L<Catmandu::Bag>, L<Catmandu::CQLSearchable>, L<Catmandu::Droppable>, L<Catmandu::Transactional>, L<MongoDB::MongoClient>
251              
252             =head1 AUTHOR
253              
254             Nicolas Steenlant, C<< <nicolas.steenlant at ugent.be> >>
255              
256             =head1 CONTRIBUTORS
257              
258             Johann Rolschewski, C<< <jorol at cpan.org> >>
259              
260             =head1 LICENSE AND COPYRIGHT
261              
262             This program is free software; you can redistribute it and/or modify it
263             under the terms of either: the GNU General Public License as published
264             by the Free Software Foundation; or the Artistic License.
265              
266             See http://dev.perl.org/licenses/ for more information.
267              
268             =cut