File Coverage

blib/lib/DBIx/Otogiri.pm
Criterion Covered Total %
statement 95 97 97.9
branch 23 28 82.1
condition 6 12 50.0
subroutine 23 23 100.0
pod 12 13 92.3
total 159 173 91.9


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