File Coverage

blib/lib/DBIx/Otogiri.pm
Criterion Covered Total %
statement 95 97 97.9
branch 23 28 82.1
condition 7 12 58.3
subroutine 23 23 100.0
pod 12 13 92.3
total 160 173 92.4


line stmt bran cond sub pod time code
1             package DBIx::Otogiri;
2 10     10   176 use 5.008005;
  10         33  
3 10     10   52 use strict;
  10         16  
  10         204  
4 10     10   45 use warnings;
  10         31  
  10         634  
5              
6             use Class::Accessor::Lite (
7 10         74 ro => [qw/connect_info strict/],
8             rw => [qw/maker owner_pid/],
9             new => 0,
10 10     10   4670 );
  10         12047  
11              
12 10     10   5778 use SQL::Maker;
  10         127610  
  10         362  
13 10     10   4471 use DBIx::Sunny;
  10         276345  
  10         510  
14 10     10   4139 use DBIx::Otogiri::Iterator;
  10         30  
  10         12275  
15              
16             sub new {
17 10     10 1 45 my ($class, %opts) = @_;
18 10         41 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         88 ) = DBI->parse_dsn($self->{connect_info}[0]);
25 10 100       389 my $strict = defined $self->strict ? $self->strict : 1;
26 10         112 $self->{dbh} = DBIx::Sunny->connect(@{$self->{connect_info}});
  10         62  
27 10         103531 $self->{maker} = SQL::Maker->new(driver => $self->{dsn}{driver}, strict => $strict);
28 10         324 $self->owner_pid($$);
29 10         102 return $self;
30             }
31              
32             sub _deflate_param {
33 21     21   51 my ($self, $table, $param) = @_;
34 21 100       77 if ($self->{deflate}) {
35 6         30 $param = $self->{deflate}->({%$param}, $table, $self);
36             }
37 21         175 return $param;
38             }
39              
40             sub _inflate_rows {
41 17     17   98 my ($self, $table, @rows) = @_;
42 17 100       57 @rows = $self->{inflate} ? map {$self->{inflate}->($_, $table, $self)} grep {defined $_} @rows : @rows;
  8         78  
  10         33  
43 17 100       190 wantarray ? @rows : $rows[0];
44             }
45              
46             sub select {
47 10     10 1 28853 my ($self, $table, $param, @opts) = @_;
48 10         39 my ($sql, @binds) = $self->maker->select($table, ['*'], $param, @opts);
49 9         2946 $self->search_by_sql($sql, \@binds, $table);
50             }
51              
52             *search = *select;
53              
54             sub search_by_sql {
55 11     11 1 2007 my ($self, $sql, $binds_aref, $table) = @_;
56              
57 11 100       51 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       19 my @binds = @{$binds_aref || []};
  9         29  
65 9         29 my $rtn = $self->dbh->select_all($sql, @binds);
66 9 50       3081 $rtn ? $self->_inflate_rows($table, @$rtn) : ();
67             }
68              
69             sub single {
70 13     13 1 6804 my ($self, $table, $param, @opts) = @_;
71 13         119 my ($sql, @binds) = $self->maker->select($table, ['*'], $param, @opts);
72 13         4945 my $row = $self->dbh->select_row($sql, @binds);
73 13 100       3481 $self->{inflate} ? $self->_inflate_rows($table, $row) : $row;
74             }
75              
76             *fetch = *single;
77              
78             sub fast_insert {
79 18     18 1 24390 my ($self, $table, $param, @opts) = @_;
80 18         58 $param = $self->_deflate_param($table, $param);
81 18         70 my ($sql, @binds) = $self->maker->insert($table, $param, @opts);
82 18         2846 $self->dbh->query($sql, @binds);
83              
84 17 100       4885 if ( defined wantarray() ) {
85 3         15 return $self->last_insert_id;
86             }
87 14         52 return;
88             }
89              
90             *insert = *fast_insert;
91              
92             sub delete {
93 2     2 1 9991 my ($self, $table, $param, @opts) = @_;
94 2         9 my ($sql, @binds) = $self->maker->delete($table, $param, @opts);
95 2         305 $self->dbh->query($sql, @binds);
96             }
97              
98             sub update {
99 3     3 1 86 my ($self, $table, $param, @opts) = @_;
100 3         7 $param = $self->_deflate_param($table, $param);
101 3         10 my ($sql, @binds) = $self->maker->update($table, $param, @opts);
102 3         682 $self->dbh->query($sql, @binds);
103             }
104              
105             sub do {
106 10     10 1 4245 my $self = shift;
107 10         31 $self->dbh->query(@_);
108             }
109              
110             sub txn_scope {
111 4     4 1 13465 my $self = shift;
112 4         13 $self->dbh->txn_scope;
113             }
114              
115             sub last_insert_id {
116 9     9 1 114 my ($self, $catalog, $schema, $table, $field, $attr_href) = @_;
117 9         25 my $driver_name = $self->{dsn}{driver};
118 9 0 33     43 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         37 return $self->{dbh}->last_insert_id($catalog, $schema, $table, $field, $attr_href);
123             }
124              
125             sub reconnect {
126 4     4 1 19 my ($self) = @_;
127              
128 4         14 $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         4000 $self->owner_pid($$);
135             }
136              
137             sub disconnect {
138 5     5 1 4999 my ($self) = @_;
139 5         9135 $self->{dbh}->disconnect();
140 5         26 $self->owner_pid(undef);
141             }
142              
143             sub dbh {
144 61     61 0 129 my ($self) = @_;
145 61         110 my $dbh = $self->{dbh};
146              
147 61 100 66     148 if ( !defined $self->owner_pid || $self->owner_pid != $$ ) {
148 1         9 $self->reconnect;
149             }
150 61 100 100     1199 if ( !$dbh->FETCH('Active') || !$dbh->ping ) {
151 2         17 $self->reconnect;
152             }
153 60         1135 return $self->{dbh};
154             }
155              
156             sub _in_transaction_check {
157 4     4   7 my ($self) = @_;
158              
159 4 100       40 return if ( !defined $self->{dbh}->{private_txt_manager} );
160              
161 1 50       11 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         183 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