File Coverage

blib/lib/DBIx/Otogiri.pm
Criterion Covered Total %
statement 117 119 98.3
branch 34 40 85.0
condition 11 21 52.3
subroutine 27 27 100.0
pod 14 15 93.3
total 203 222 91.4


line stmt bran cond sub pod time code
1             package DBIx::Otogiri;
2 14     14   305310 use 5.008005;
  14         45  
3 14     14   64 use strict;
  14         42  
  14         346  
4 14     14   74 use warnings;
  14         29  
  14         956  
5              
6             use Class::Accessor::Lite (
7 14         108 ro => [qw/connect_info strict dburl/],
8             rw => [qw/maker owner_pid row_class_schema/],
9             new => 0,
10 14     14   6283 );
  14         16657  
11              
12 14     14   10249 use SQL::Maker;
  14         189026  
  14         505  
13 14     14   7501 use DBIx::Sunny;
  14         439687  
  14         927  
14 14     14   5890 use DBIx::Otogiri::Iterator;
  14         38  
  14         422  
15 14     14   7757 use URI::db;
  14         237087  
  14         17945  
16              
17             sub new {
18 15     15 1 605630 my ($class, %opts) = @_;
19 15         70 my $self = bless {%opts}, $class;
20 15 100       111 if ($self->{dburl}) {
21 1         11 my $dburl = URI::db->new($self->{dburl});
22 1         1291 $self->{connect_info} = [$dburl->dbi_dsn, $dburl->user, $dburl->password];
23             }
24             ( $self->{dsn}{scheme},
25             $self->{dsn}{driver},
26             $self->{dsn}{attr_str},
27             $self->{dsn}{attributes},
28             $self->{dsn}{driver_dsn}
29 15         1067 ) = DBI->parse_dsn($self->{connect_info}[0]);
30 15 100       609 my $strict = defined $self->strict ? $self->strict : 1;
31 15         165 $self->{dbh} = DBIx::Sunny->connect(@{$self->{connect_info}});
  15         1296  
32 15         186735 $self->{maker} = SQL::Maker->new(driver => $self->{dsn}{driver}, strict => $strict);
33 15         614 $self->owner_pid($$);
34 15         242 return $self;
35             }
36              
37             sub row_class {
38 4     4 1 4853 my ($self, $class_name) = @_;
39 4 100       14 if ($class_name) {
40 2         9 $self->row_class_schema($class_name);
41             }
42 4         30 return $self;
43             }
44              
45             sub no_row_class {
46 2     2 1 3233 my ($self) = @_;
47 2         6 delete $self->{row_class_schema};
48 2         12 return $self;
49             }
50              
51             sub _deflate_param {
52 24     24   50 my ($self, $table, $param) = @_;
53 24 100       122 if ($self->{deflate}) {
54 6         30 $param = $self->{deflate}->({%$param}, $table, $self);
55             }
56 24         242 return $param;
57             }
58              
59             sub _inflate_rows {
60 19     19   77 my ($self, $table, @rows) = @_;
61 19 100       55 @rows = $self->{inflate} ? map {$self->{inflate}->($_, $table, $self)} grep {defined $_} @rows : @rows;
  8         74  
  10         27  
62 19 100       237 wantarray ? @rows : $rows[0];
63             }
64              
65             sub select {
66 12     12 1 18266 my ($self, $table, $param, @opts) = @_;
67 12         40 my ($sql, @binds) = $self->maker->select($table, ['*'], $param, @opts);
68 11         3059 $self->search_by_sql($sql, \@binds, $table);
69             }
70              
71             *search = *select;
72              
73             sub search_by_sql {
74 13     13 1 1869 my ($self, $sql, $binds_aref, $table) = @_;
75              
76 13 100       47 return DBIx::Otogiri::Iterator->new(
77             db => $self,
78             sql => $sql,
79             binds => $binds_aref,
80             table => $table,
81             ) unless wantarray;
82              
83 11 50       12 my @binds = @{$binds_aref || []};
  11         25  
84 11         29 my $dbh = $self->dbh;
85 11         24 my $row_class = $self->row_class_schema;
86 11 100       70 my $rtn = $row_class ? $dbh->select_all_as($row_class, $sql, @binds) : $dbh->select_all($sql, @binds);
87 11 50       3189 $rtn ? $self->_inflate_rows($table, @$rtn) : ();
88             }
89              
90             sub single {
91 16     16 1 3072 my ($self, $table, $param, @opts) = @_;
92 16         60 my ($sql, @binds) = $self->maker->select($table, ['*'], $param, @opts);
93 16         6429 my $dbh = $self->dbh;
94 16         59 my $row_class = $self->row_class_schema;
95 16 100       182 my $row = $row_class ? $dbh->select_row_as($row_class, $sql, @binds) : $dbh->select_row($sql, @binds);
96 16 100       4354 $self->{inflate} ? $self->_inflate_rows($table, $row) : $row;
97             }
98              
99             *fetch = *single;
100              
101             sub fast_insert {
102 21     21 1 29717 my ($self, $table, $param, @opts) = @_;
103 21         85 $param = $self->_deflate_param($table, $param);
104 21         148 my ($sql, @binds) = $self->maker->insert($table, $param, @opts);
105 21         3155 $self->dbh->query($sql, @binds);
106              
107 20 100       5637 if ( defined wantarray() ) {
108 3         12 return $self->last_insert_id;
109             }
110 17         68 return;
111             }
112              
113             *insert = *fast_insert;
114              
115             sub delete {
116 2     2 1 8059 my ($self, $table, $param, @opts) = @_;
117 2         6 my ($sql, @binds) = $self->maker->delete($table, $param, @opts);
118 2         285 $self->dbh->query($sql, @binds);
119             }
120              
121             sub update {
122 3     3 1 69 my ($self, $table, $param, @opts) = @_;
123 3 100       9 if (ref $param eq 'HASH') {
124 1         2 $param = [%$param];
125             }
126 3         8 $param = $self->_deflate_param($table, $param);
127 3         6 my ($sql, @binds) = $self->maker->update($table, $param, @opts);
128 3         525 $self->dbh->query($sql, @binds);
129             }
130              
131             sub do {
132 12     12 1 1898 my $self = shift;
133 12         82 $self->dbh->query(@_);
134             }
135              
136             sub txn_scope {
137 4     4 1 5747 my $self = shift;
138 4         15 $self->dbh->txn_scope;
139             }
140              
141             sub last_insert_id {
142 11     11 1 113 my ($self, $catalog, $schema, $table, $field, $attr_href) = @_;
143 11         40 my $driver_name = $self->{dsn}{driver};
144 11 0 33     42 if ($driver_name eq 'Pg' && !defined $table && !exists $attr_href->{sequence}) {
      33        
145 0         0 my @rows = $self->search_by_sql('SELECT LASTVAL() AS lastval');
146 0         0 return $rows[0]->{lastval};
147             }
148 11         41 return $self->{dbh}->last_insert_id($catalog, $schema, $table, $field, $attr_href);
149             }
150              
151             sub reconnect {
152 6     6 1 34 my ($self) = @_;
153              
154 6         74 $self->_in_transaction_check();
155              
156 5         67 $self->disconnect();
157              
158 5         41 my $dbh = $self->{dbh};
159 5         217 $self->{dbh} = $dbh->clone();
160 5         10520 $self->owner_pid($$);
161             }
162              
163             sub disconnect {
164 22     22 1 10815 my ($self) = @_;
165 22         13265 $self->{dbh}->disconnect();
166 22         104 $self->owner_pid(undef);
167             }
168              
169             sub dbh {
170 75     75 0 242 my ($self) = @_;
171 75         147 my $dbh = $self->{dbh};
172              
173 75 100 100     295 if ( !defined $self->owner_pid || $self->owner_pid != $$ ) {
174 2         287 $self->reconnect;
175             }
176 75 100 100     1778 if ( !$dbh->FETCH('Active') || !$dbh->ping ) {
177 3         28 $self->reconnect;
178             }
179 74         1474 return $self->{dbh};
180             }
181              
182             sub _in_transaction_check {
183 6     6   19 my ($self) = @_;
184              
185 6 100       363 return if ( !defined $self->{dbh}->{private_txt_manager} );
186              
187 1 50       11 if ( my $info = $self->{dbh}->{private_txt_manager}->in_transaction() ) {
188 1         12 my $caller = $info->{caller};
189 1         4 my $pid = $info->{pid};
190 1         594 Carp::confess("Detected transaction during a connect operation (last known transaction at $caller->[1] line $caller->[2], pid $pid). Refusing to proceed at");
191             }
192             }
193              
194             sub DESTROY {
195 15     15   29341 my ($self) = @_;
196            
197             # Automatically call disconnect when the object is destroyed or program terminates
198             # Skip calling disconnect in forked processes (only call in the process that owns the connection)
199 15 50 33     463 if ($self->{dbh} &&
      33        
      33        
200             $self->{dbh}->FETCH('Active') &&
201             defined $self->owner_pid &&
202             $self->owner_pid == $$) {
203 15         312 $self->disconnect();
204             }
205             }
206              
207              
208             1;
209             __END__
210              
211             =encoding utf-8
212              
213             =head1 NAME
214              
215             DBIx::Otogiri - Core of Otogiri
216              
217             =head1 SYNOPSIS
218              
219             use Otogiri;
220             my $db = Otogiri->new(connect_info => ['dbi:SQLite:...', '', '']);
221            
222             # or use with DBURL
223             my $db = Otogiri->new(dburl => 'sqlite://...');
224            
225             $db->insert(book => {title => 'mybook1', author => 'me', ...});
226              
227             my $book_id = $db->last_insert_id;
228             my $row = $db->single(book => {id => $book_id});
229              
230             print 'Title: '. $row->{title}. "\n";
231            
232             my @rows = $db->select(book => {price => {'>=' => 500}});
233             for my $r (@rows) {
234             printf "Title: %s \nPrice: %s yen\n", $r->{title}, $r->{price};
235             }
236              
237             # If you using perl 5.38 or later, you can use class feature.
238             class Book {
239             field $id :param;
240             field $title :param;
241             field $author :param;
242             field $price :param;
243             field $created_at :param;
244             field $updated_at :param;
245              
246             method title {
247             return $title;
248             }
249             };
250             my $book = $db->row_class('Book')->single(book => {id => 1}); # $book is Book object.
251             say $book->title; # => say book title.
252            
253             my $hash = $db->no_row_class->single(book => {id => 1}); # $hash is HASH reference.
254             say $hash->{title}; # => say book title.
255              
256             $db->update(book => {author => 'oreore'}, {author => 'me'});
257            
258             $db->delete(book => {author => 'me'});
259            
260             ### using transaction
261             do {
262             my $txn = $db->txn_scope;
263             $db->insert(book => ...);
264             $db->insert(store => ...);
265             $txn->commit;
266             };
267              
268             =head1 DESCRIPTION
269              
270             DBIx::Otogiri is core feature class of Otogiri.
271              
272             =head1 ATTRIBUTES
273              
274             =head2 connect_info (required)
275              
276             connect_info => [$dsn, $dbuser, $dbpass],
277              
278             You have to specify C<dsn>, C<dbuser>, and C<dbpass>, to connect to database.
279              
280             =head2 strict (optional, default is 1)
281              
282             In strict mode, all the expressions must be declared by using blessed references that export as_sql and bind methods like SQL::QueryMaker.
283              
284             Please see METHODS section of L<SQL::Maker>'s documentation.
285              
286             =head2 inflate (optional)
287              
288             use JSON;
289             inflate => sub {
290             my ($data, $tablename, $db) = @_;
291             if (defined $data->{json}) {
292             $data->{json} = decode_json($data->{json});
293             }
294             $data->{table} = $tablename;
295             $data;
296             },
297              
298             You may specify column inflation logic.
299              
300             Specified code is called internally when called select(), search_by_sql(), and single().
301              
302             C<$db> is Otogiri instance, you can use Otogiri's method in inflate logic.
303              
304             =head2 deflate (optional)
305              
306             use JSON;
307             deflate => sub {
308             my ($data, $tablename, $db) = @_;
309             if (defined $data->{json}) {
310             $data->{json} = encode_json($data->{json});
311             }
312             delete $data->{table};
313             $data;
314             },
315              
316             You may specify column deflation logic.
317              
318             Specified code is called internally when called insert(), update(), and delete().
319              
320             C<$db> is Otogiri instance, you can use Otogiri's method in deflate logic.
321              
322             =head1 METHODS
323              
324             =head2 new
325              
326             my $db = DBIx::Otogiri->new( connect_info => [$dsn, $dbuser, $dbpass] );
327              
328             Instantiate and connect to db.
329              
330             Please see ATTRIBUTE section.
331              
332             =head2 insert / fast_insert
333              
334             my $last_insert_id = $db->insert($table_name => $columns_in_hashref);
335              
336             Insert a data simply.
337              
338             =head2 search
339              
340             =head2 select / search
341              
342             ### receive rows of result in array
343             my @rows = $db->search($table_name => $conditions_in_hashref [,@options]);
344            
345             ### or we can receive result as iterator object
346             my $iter = $db->search($table_name => $conditions_in_hashref [,@options]);
347            
348             while (my $row = $iter->next) {
349             ... any logic you want ...
350             }
351            
352             printf "rows = %s\n", $iter->fetched_count;
353              
354             Select from specified table. When you receive result by array, it returns matched rows. Or not, it returns a result as L<DBIx::Otogiri::Iterator> object.
355              
356             =head2 single / fetch
357              
358             my $row = $db->fetch($table_name => $conditions_in_hashref [,@options]);
359              
360             Select from specified table. Then, returns first of matched rows.
361              
362             =head2 search_by_sql
363              
364             my @rows = $db->search_by_sql($sql, \@bind_vals [, $table_name]);
365              
366             Select by specified SQL. Then, returns matched rows as array. $table_name is optional and used for inflate parameter.
367              
368             =head2 row_class
369              
370             class Book {
371             field $id :param;
372             field $title :param;
373             field $author :param;
374             field $price :param;
375             field $created_at :param;
376             field $updated_at :param;
377              
378             method title {
379             return $title;
380             }
381             };
382              
383             my $db = $db->row_class($class_name);
384              
385             Set row class name. If you set row class name, you can receive result as row class object.
386              
387             =head2 no_row_class
388              
389             my $db = $db->no_row_class;
390              
391             Unset row class name. If you unset row class name, you can receive result as HASH reference.
392              
393             =head2 update
394              
395             $db->update($table_name => {update_col_1 => $new_value_1, ...}, $conditions_in_hashref);
396              
397             Update rows that matched to $conditions_in_hashref.
398              
399             =head2 delete
400              
401             $db->delete($table_name => $conditions_in_hashref);
402              
403             Delete rows that matched to $conditions_in_hashref.
404              
405             =head2 do
406              
407             $db->do($sql, @bind_vals);
408              
409             Execute specified SQL.
410              
411             =head2 txn_scope
412              
413             my $txn = $db->txn_scope;
414              
415             returns DBIx::TransactionManager::ScopeGuard's instance. See L<DBIx::TransactionManager> to more information.
416              
417             =head2 last_insert_id
418              
419             my $id = $db->last_insert_id([@args]);
420              
421             returns last_insert_id. (mysql_insertid in MySQL or last_insert_rowid in SQLite)
422              
423             =head2 disconnect
424              
425             disconnect database.
426              
427             Note: Since version with auto-disconnect feature, disconnect() is automatically called when the object is destroyed (at program termination or when the object goes out of scope), but only in the process that originally created the connection (fork-safe).
428              
429             =head2 reconnect
430              
431             reconnect database.
432              
433              
434             =head1 LICENSE
435              
436             Copyright (C) ytnobody.
437              
438             This library is free software; you can redistribute it and/or modify
439             it under the same terms as Perl itself.
440              
441             =head1 AUTHOR
442              
443             ytnobody E<lt>ytnobody@gmail.comE<gt>
444              
445             =head1 SEE ALSO
446              
447             L<DBIx::Sunny>
448              
449             L<SQL::Maker>
450              
451             L<DBIx::Otogiri::Iterator>
452              
453             =cut
454