File Coverage

blib/lib/Catmandu/Store/DBI.pm
Criterion Covered Total %
statement 56 77 72.7
branch 9 24 37.5
condition 1 2 50.0
subroutine 13 18 72.2
pod 0 8 0.0
total 79 129 61.2


line stmt bran cond sub pod time code
1              
2             use Catmandu::Sane;
3 6     6   103445 use Catmandu::Util qw(require_package);
  6         964945  
  6         45  
4 6     6   1634 use DBI;
  6         12  
  6         275  
5 6     6   8267 use Catmandu::Store::DBI::Bag;
  6         97799  
  6         410  
6 6     6   3321 use Moo;
  6         24  
  6         272  
7 6     6   52 use MooX::Aliases;
  6         13  
  6         30  
8 6     6   2119 use Catmandu::Error;
  6         15  
  6         44  
9 6     6   2408 use namespace::clean;
  6         12  
  6         161  
10 6     6   28  
  6         10  
  6         37  
11             our $VERSION = "0.12";
12              
13             with 'Catmandu::Store';
14             with 'Catmandu::Transactional';
15              
16             has data_source => (
17             is => 'ro',
18             required => 1,
19             alias => 'dsn',
20             trigger => sub {
21             my $ds = $_[0]->{data_source};
22             $ds = $ds =~ /^DBI:/i ? $ds : "DBI:$ds";
23             $_[0]->{data_source} = $ds;
24             },
25             );
26             has username => (is => 'ro', default => sub {''}, alias => 'user');
27             has password => (is => 'ro', default => sub {''}, alias => 'pass');
28             has default_order => (is => 'ro', default => sub {'ID'});
29             has handler => (is => 'lazy');
30             has _in_transaction => (is => 'rw', writer => '_set_in_transaction',);
31             has _dbh => (is => 'lazy', builder => '_build_dbh', writer => '_set_dbh',);
32              
33             # DEPRECATED methods. Were only invented to tackle of problem of reconnection
34             warn "method timeout has been replaced by auto reconnect";
35             }
36 0     0 0 0  
37             warn "method has_timeout has been replaced by auto reconnect";
38             0;
39             }
40 0     0 0 0  
41 0         0 warn "method reconnect_after_timeout has been replaced by auto reconnect";
42             }
43              
44             'Catmandu::Store::DBI::Handler';
45 0     0 0 0 }
46              
47             my ($self) = @_;
48             my $driver = $self->dbh->{Driver}{Name} // '';
49 5     5 0 11 my $ns = $self->handler_namespace;
50             my $pkg;
51             if ($driver =~ /pg/i) {
52             $pkg = 'Pg';
53 5     5   43 }
54 5   50     17 elsif ($driver =~ /sqlite/i) {
55 5         199 $pkg = 'SQLite';
56 5         14 }
57 5 50       43 elsif ($driver =~ /mysql/i) {
    50          
    0          
58 0         0 $pkg = 'MySQL';
59             }
60             else {
61 5         12 Catmandu::NotImplemented->throw(
62             'Only Pg, SQLite and MySQL are supported.');
63             }
64 0         0 require_package($pkg, $ns)->new;
65             }
66              
67 0         0 my ($self) = @_;
68             my $opts = {
69             AutoCommit => 1,
70 5         28 RaiseError => 1,
71             mysql_auto_reconnect => 1,
72             mysql_enable_utf8 => 1,
73             pg_utf8_strings => 1,
74 5     5   39 sqlite_use_immediate_transaction => 1,
75 5         31 sqlite_unicode => 1,
76             };
77             my $dbh
78             = DBI->connect($self->data_source, $self->username, $self->password,
79             $opts,);
80             $dbh;
81             }
82              
83              
84 5         50 my $self = $_[0];
85             my $dbh = $self->_dbh;
86              
87 5         8395 # reconnect when dbh is not set (should never happen)
88             return $self->reconnect
89             unless defined $dbh;
90              
91             # check validity of dbh
92 102     102 0 963 # for performance reasons only check every second
93 102         1583 if ( defined( $self->{last_ping_t} ) ) {
94              
95             return $dbh if (time - $self->{last_ping_t}) < 1;
96 102 50       746  
97             }
98              
99             $self->{last_ping_t} = time;
100             return $dbh if $dbh->ping;
101 102 100       248  
102             # one should never reconnect to a database during a transaction
103 97 50       445 # because that would initiate a new transaction
104             Catmandu::Error->throw("Connection to DBI backend lost, and cannot reconnect during a transaction")
105             unless $dbh->{AutoCommit};
106              
107 5         13 # reconnect and return dbh
108 5 50       23 # note: mysql_auto_reconnect only works when AutoCommit is 1
109             $self->reconnect;
110              
111             }
112              
113 0 0       0  
114             my $self = $_[0];
115             my $dbh = $self->_dbh;
116             $dbh->disconnect if defined($dbh);
117 0         0 $self->_set_dbh($self->_build_dbh);
118             $self->_dbh;
119              
120             }
121              
122             my ($self, $sub) = @_;
123 0     0 0 0  
124 0         0 if ($self->_in_transaction) {
125 0 0       0 return $sub->();
126 0         0 }
127 0         0  
128             my $dbh = $self->dbh;
129             my @res;
130              
131             eval {
132 1     1 0 49 $self->_set_in_transaction(1);
133             $dbh->begin_work;
134 1 50       9 @res = $sub->();
135 0         0 $dbh->commit;
136             $self->_set_in_transaction(0);
137             1;
138 1         3 } or do {
139 1         3 my $err = $@;
140             eval {$dbh->rollback};
141             $self->_set_in_transaction(0);
142 1         6 die $err;
143 1         15 };
144 1         29  
145 0         0 @res;
146 0         0 }
147 0         0  
148 1 50       3 my ($self) = @_;
149 1         19 $self->{_dbh}->disconnect if $self->{_dbh};
150 1         2 }
  1         187  
151 1         9  
152 1         12 1;
153              
154              
155 0           =pod
156              
157             =encoding utf8
158              
159 0     0 0   =head1 NAME
160 0 0          
161             Catmandu::Store::DBI - A Catmandu::Store backed by DBI
162              
163             =head1 VERSION
164              
165             Version 0.0424
166              
167             =head1 SYNOPSIS
168              
169             # From the command line
170             $ catmandu import JSON to DBI --data_source SQLite:mydb.sqlite < data.json
171              
172             # Or via a configuration file
173             $ cat catmandu.yml
174             ---
175             store:
176             mydb:
177             package: DBI
178             options:
179             data_source: "dbi:mysql:database=mydb"
180             username: xyz
181             password: xyz
182             ...
183             $ catmandu import JSON to mydb < data.json
184             $ catmandu export mydb to YAML > data.yml
185             $ catmandu export mydb --id 012E929E-FF44-11E6-B956-AE2804ED5190 to JSON > record.json
186             $ catmandu count mydb
187             $ catmandy delete mydb
188              
189             # From perl
190             use Catmandu::Store::DBI;
191              
192             my $store = Catmandu::Store::DBI->new(
193             data_source => 'DBI:mysql:database=mydb', # prefix "DBI:" optional
194             username => 'xyz', # optional
195             password => 'xyz', # optional
196             );
197              
198             my $obj1 = $store->bag->add({ name => 'Patrick' });
199              
200             printf "obj1 stored as %s\n" , $obj1->{_id};
201              
202             # Force an id in the store
203             my $obj2 = $store->bag->add({ _id => 'test123' , name => 'Nicolas' });
204              
205             my $obj3 = $store->bag->get('test123');
206              
207             $store->bag->delete('test123');
208              
209             $store->bag->delete_all;
210              
211             # All bags are iterators
212             $store->bag->each(sub { ... });
213             $store->bag->take(10)->each(sub { ... });
214              
215             =head1 DESCRIPTION
216              
217             A Catmandu::Store::DBI is a Perl package that can store data into
218             DBI backed databases. The database as a whole is a 'store'
219             L<Catmandu::Store>. Databases tables are 'bags' (L<Catmandu::Bag>).
220              
221             Databases need to be preconfigured for accepting Catmandu data. When
222             no specialized Catmandu tables exist in a database then Catmandu will
223             create them automatically. See "DATABASE CONFIGURATION" below.
224              
225             DO NOT USE Catmandu::Store::DBI on an existing database! Tables and
226             data can be deleted and changed.
227              
228             =head1 LIMITATIONS
229              
230             Currently only MySQL, Postgres and SQLite are supported. Text columns are also
231             assumed to be utf-8.
232              
233             =head1 CONFIGURATION
234              
235             =over
236              
237             =item data_source
238              
239             Required. The connection parameters to the database. See L<DBI> for more information.
240              
241             Examples:
242              
243             dbi:mysql:foobar <= a local mysql database 'foobar'
244             dbi:Pg:dbname=foobar;host=myserver.org;port=5432 <= a remote PostGres database
245             dbi:SQLite:mydb.sqlite <= a local SQLLite file based database mydb.sqlite
246             dbi:Oracle:host=myserver.org;sid=data01 <= a remote Oracle database
247              
248             Drivers for each database need to be available on your computer. Install then with:
249              
250             cpanm DBD::mysql
251             cpanm DBD::Pg
252             cpanm DBD::SQLite
253              
254             =item user
255              
256             Optional. A user name to connect to the database
257              
258             =item password
259              
260             Optional. A password for connecting to the database
261              
262             =item default_order
263              
264             Optional. Default the default sorting of results when returning an iterator.
265             Choose 'ID' to order on the configured identifier field, 'NONE' to skip all
266             ordering, or "$field" where $field is the name of a table column. By default
267             set to 'ID'.
268              
269             =back
270              
271             =head1 DATABASE CONFIGURATION
272              
273             When no tables exists for storing data in the database, then Catmandu
274             will create them. By default tables are created for each L<Catmandu::Bag>
275             which contain an '_id' and 'data' column.
276              
277             This behavior can be changed with mapping option:
278              
279             my $store = Catmandu::Store::DBI->new(
280             data_source => 'DBI:mysql:database=test',
281             bags => {
282             # books table
283             books => {
284             mapping => {
285             # these keys will be directly mapped to columns
286             # all other keys will be serialized in the data column
287             title => {type => 'string', required => 1, column => 'book_title'},
288             isbn => {type => 'string', unique => 1},
289             authors => {type => 'string', array => 1}
290             }
291             }
292             }
293             );
294              
295             For keys that have a corresponding table column configured, the method 'select' of class L<Catmandu::Store::DBI::Bag> provides
296             a more efficiënt way to query records.
297              
298             See L<Catmandu::Store::DBI::Bag> for more information.
299              
300             =head2 Column types
301              
302             =over
303              
304             =item string
305              
306             =item integer
307              
308             =item binary
309              
310             =item datetime
311              
312             Only MySQL, PostgreSQL
313              
314             =item datetime_milli
315              
316             Only MySQL, PostgreSQL
317              
318             =item json
319              
320             Only PostgreSQL
321              
322             This is mapped internally to postgres field of type "jsonb".
323              
324             Please use the serializer L<Catmandu::Serializer::json_string>,
325              
326             if you choose to store the perl data structure into this type of field.
327              
328             Reasons:
329              
330             * there are several types of serializers. E.g. serializer "messagepack"
331             produces a string that is not accepted by a jsonb field in postgres
332              
333             * the default serializer L<Catmandu::Serializer::json> converts the perl data structure to a binary json string,
334             and the DBI client reencodes that utf8 string (because jsonb is a sort of text field),
335             so you end up having a double encoded string.
336              
337             =back
338              
339             =head2 Column options
340              
341             =over
342              
343             =item column
344              
345             Name of the table column if it differs from the key in your data.
346              
347             =item array
348              
349             Boolean option, default is C<0>. Note that this is only supported for PostgreSQL.
350              
351             =item unique
352              
353             Boolean option, default is C<0>.
354              
355             =item index
356              
357             Boolean option, default is C<0>. Ignored if C<unique> is true.
358              
359             =item required
360              
361             Boolean option, default is C<0>.
362              
363             =back
364              
365             =head1 AUTO RECONNECT
366              
367             This library automatically connects to the underlying
368              
369             database, and reconnects when that connection is lost.
370              
371             There is one exception though: when the connection is lost
372              
373             in the middle of a transaction, this is skipped and
374              
375             a L<Catmandu::Error> is thrown. Reconnecting during a
376              
377             transaction would have returned a new transaction,
378              
379             and (probably?) committed the lost transaction
380              
381             contrary to your expectation. There is actually no way to
382              
383             recover from that, so throwing an error seemed
384              
385             liked to a "good" way to solve that.
386              
387              
388             In order to avoid this situation, try to avoid
389              
390             a big time lap between database actions during
391              
392             a transaction, as your server may have thrown
393              
394             you out.
395              
396             P.S. the mysql option C<< mysql_auto_reconnect >>
397              
398             does NOT automatically reconnect during a transaction
399              
400             exactly for this reason.
401              
402             =head1 SEE ALSO
403              
404             L<Catmandu::Bag>, L<DBI>
405              
406             =cut