File Coverage

blib/lib/DBIx/Otogiri.pm
Criterion Covered Total %
statement 102 108 94.4
branch 26 34 76.4
condition 7 12 58.3
subroutine 24 25 96.0
pod 14 15 93.3
total 173 194 89.1


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