File Coverage

blib/lib/DBIx/Sunny.pm
Criterion Covered Total %
statement 23 119 19.3
branch 0 30 0.0
condition 0 27 0.0
subroutine 8 24 33.3
pod 1 1 100.0
total 32 201 15.9


line stmt bran cond sub pod time code
1             package DBIx::Sunny;
2              
3 10     10   1114931 use strict;
  10         110  
  10         294  
4 10     10   55 use warnings;
  10         21  
  10         255  
5 10     10   289 use 5.008005;
  10         39  
6 10     10   17397 use DBI 1.615;
  10         183087  
  10         1215  
7              
8             our $VERSION = '0.9993';
9             our $SKIP_CALLER_REGEX = qr/^(:?DBIx?|DBD|Try::Tiny|Context::Preserve)\b/;
10              
11 10     10   4063 use parent qw/DBI/;
  10         2476  
  10         64  
12              
13             sub connect {
14 0     0 1   my $class = shift;
15 0           my ($dsn, $user, $pass, $attr) = @_;
16 0           $attr->{RaiseError} = 1;
17 0           $attr->{PrintError} = 0;
18 0           $attr->{ShowErrorStatement} = 1;
19 0           $attr->{AutoInactiveDestroy} = 1;
20 0 0         if ($dsn =~ /^(?i:dbi):SQLite:/) {
21 0           $attr->{sqlite_use_immediate_transaction} = 1;
22 0 0         $attr->{sqlite_unicode} = 1 unless exists $attr->{sqlite_unicode};
23             }
24 0 0 0       if ($dsn =~ /^(?i:dbi):mysql:/ && ! exists $attr->{mysql_enable_utf8} && ! exists $attr->{mysql_enable_utf8mb4} ) {
      0        
25 0           $attr->{mysql_enable_utf8} = 1;
26             }
27 0 0 0       if ($dsn =~ /^(?i:dbi):Pg:/ && ! exists $attr->{pg_enable_utf8}) {
28 0           $attr->{pg_enable_utf8} = 1;
29             }
30 0           $class->SUPER::connect($dsn, $user, $pass, $attr);
31             }
32              
33             package DBIx::Sunny::db;
34             our @ISA = qw(DBI::db);
35              
36 10     10   6970 use DBIx::Sunny::Util qw/bind_and_execute expand_placeholder/;
  10         26  
  10         687  
37 10     10   4722 use DBIx::TransactionManager 0.13;
  10         36224  
  10         368  
38 10     10   86 use Scalar::Util qw/weaken/;
  10         19  
  10         12087  
39              
40             sub connected {
41 0     0     my $dbh = shift;
42 0           my ($dsn, $user, $pass, $attr) = @_;
43 0           $dbh->{RaiseError} = 1;
44 0           $dbh->{PrintError} = 0;
45 0           $dbh->{ShowErrorStatement} = 1;
46 0           $dbh->{AutoInactiveDestroy} = 1;
47 0 0         if ($dsn =~ /^dbi:SQLite:/) {
48 0           $dbh->{sqlite_use_immediate_transaction} = 1;
49 0 0         $dbh->{sqlite_unicode} = 1 unless exists $attr->{sqlite_unicode};
50              
51 0           $dbh->do("PRAGMA journal_mode = WAL");
52 0           $dbh->do("PRAGMA synchronous = NORMAL");
53              
54             }
55 0 0 0       if ($dsn =~ /^dbi:mysql:/ && ! exists $attr->{mysql_enable_utf8} && ! exists $attr->{mysql_enable_utf8mb4} ) {
      0        
56 0           $dbh->{mysql_enable_utf8} = 1;
57 0           $dbh->do("SET NAMES utf8");
58             }
59 0 0         if ($dsn =~ /^dbi:mysql:/) {
60 0           $dbh->{mysql_auto_reconnect} = 0;
61             }
62 0           $dbh->{private_connect_info} = [@_];
63 0           $dbh->SUPER::connected(@_);
64             }
65              
66 0     0     sub connect_info { $_[0]->{private_connect_info} }
67              
68             sub txn_scope {
69 0     0     my $self = shift;
70 0 0         if ( ! $self->{private_txt_manager} ) {
71 0           $self->{private_txt_manager} = DBIx::TransactionManager->new($self);
72 0           weaken($self->{private_txt_manager}->{dbh});
73             }
74             $self->{private_txt_manager}->txn_scope(
75 0           caller => [caller(0)]
76             );
77             }
78              
79             sub __set_comment {
80 0     0     my $self = shift;
81 0           my $query = shift;
82              
83 0           my $trace;
84 0           my $i = 0;
85 0           while ( my @caller = caller($i) ) {
86 0           my $file = $caller[1];
87 0           $file =~ s!\*/!*\//!g;
88 0           $trace = "/* $file line $caller[2] */";
89 0 0 0       last if $caller[0] ne ref($self) && $caller[0] !~ $SKIP_CALLER_REGEX;
90 0           $i++;
91             }
92 0           $query =~ s! ! $trace !;
93 0           $query;
94             }
95              
96             sub prepare {
97 0     0     my $self = shift;
98 0           my $query = shift;
99 0           $self->SUPER::prepare($self->__set_comment($query), @_);
100             }
101              
102             sub do {
103 0     0     my $self = shift;
104 0           my ($query, $attr, @bind) = @_;
105 0           $self->SUPER::do($self->__set_comment($query), $attr, @bind);
106             }
107              
108             sub fill_arrayref {
109 0     0     my $self = shift;
110 0           return expand_placeholder(@_);
111             }
112              
113             sub __prepare_and_execute {
114 0     0     my $self = shift;
115 0           my ($query, @bind) = expand_placeholder(@_);
116 0           my $sth = $self->prepare($query);
117 0           my $ret = bind_and_execute($sth, @bind);
118 0           return ($sth, $ret);
119             }
120              
121             sub select_one {
122 0     0     my $self = shift;
123 0           my ($sth, $ret) = $self->__prepare_and_execute(@_);
124 0   0       my $row = $ret && $sth->fetchrow_arrayref;
125 0 0         return unless $row;
126 0           return $row->[0];
127             }
128              
129             sub select_row {
130 0     0     my $self = shift;
131 0           my ($sth, $ret) = $self->__prepare_and_execute(@_);
132 0   0       my $row = $ret && $sth->fetchrow_hashref;
133 0 0         return unless $row;
134 0           return $row;
135             }
136              
137             sub select_all {
138 0     0     my $self = shift;
139 0           my ($sth, $ret) = $self->__prepare_and_execute(@_);
140 0   0       my $rows = $ret && $sth->fetchall_arrayref({});
141 0           return $rows;
142             }
143              
144             sub query {
145 0     0     my $self = shift;
146 0           (undef, my $ret) = $self->__prepare_and_execute(@_);
147 0           return $ret;
148             }
149              
150             sub last_insert_id {
151 0     0     my $self = shift;
152 0           my $dsn = $self->connect_info->[0];
153 0 0         if ($dsn =~ /^(?i:dbi):SQLite:/) {
    0          
154 0           return $self->func('last_insert_rowid');
155             }
156             elsif ( $dsn =~ /^(?i:dbi):mysql:/) {
157 0           return $self->{mysql_insertid};
158             }
159 0           $self->SUPER::last_insert_id(@_);
160             }
161              
162             sub select_row_as {
163 0     0     my $self = shift;
164 0           my $model = shift;
165 0           my $row = $self->select_row(@_);
166 0 0         return unless $row;
167 0           $model->new(%$row);
168             }
169              
170             sub select_all_as {
171 0     0     my $self = shift;
172 0           my $model = shift;
173 0           my $rows = $self->select_all(@_);
174 0           return [ map { $model->new(%$_) } @$rows ];
  0            
175             }
176              
177             package DBIx::Sunny::st; # statement handler
178             our @ISA = qw(DBI::st);
179              
180             1;
181              
182             __END__
183              
184             =encoding utf8
185              
186             =head1 NAME
187              
188             DBIx::Sunny - Simple DBI wrapper
189              
190             =head1 SYNOPSIS
191              
192             use DBIx::Sunny;
193              
194             my $dbh = DBIx::Sunny->connect(...);
195              
196             # or
197              
198             use DBI;
199              
200             my $dbh = DBI->connect(.., {
201             RootClass => 'DBIx::Sunny',
202             PrintError => 0,
203             RaiseError => 1,
204             });
205              
206             =head1 DESCRIPTION
207              
208             DBIx::Sunny is a simple DBI wrapper. It provides better usability for you. This module based on Amon2::DBI.
209             DBIx::Sunny supports only SQLite and MySQL.
210              
211             =head1 FEATURES
212              
213             =over 4
214              
215             =item Set AutoInactiveDestroy to true.
216              
217             DBIx::Sunny sets AutoInactiveDestroy as true.
218              
219             =item [SQLite/MySQL/Pg] Auto encode/decode UTF-8
220              
221             DBIx::Sunny sets sqlite_unicode, mysql_enable_utf8 and pg_enable_utf8 automatically.
222              
223             =item [SQLite] Performance tuning
224              
225             DBIx::Sunny sets sqlite_use_immediate_transaction to true, and executes these PRAGMA statements
226              
227             PRAGMA journal_mode = WAL
228             PRAGMA synchronous = NORMAL
229              
230             =item Nested transaction management.
231              
232             DBIx::Sunny supports nested transaction management based on RAII like DBIx::Class or DBIx::Skinny. It uses L<DBIx::TransactionManager> internally.
233              
234             =item Error Handling
235              
236             DBIx::Sunny sets RaiseError and ShowErrorStatement as true. DBIx::Sunny raises exception and shows current statement if your $dbh occurred exception.
237              
238             =item SQL comment
239              
240             DBIx::Sunny adds file name and line number as SQL comment that invokes SQL statement.
241              
242             =item Easy access to last_insert_id
243              
244             DBIx::Sunny's last_insert_id needs no arguments. It's shortcut for mysql_insertid or last_insert_rowid.
245              
246             =item Auto expanding arrayref bind parameters
247              
248             select_(one|row|all) and query methods support auto-expanding arrayref bind parameters.
249              
250             $dbh->select_all('SELECT * FROM id IN (?)', [1 2 3])
251             #SQL: 'SELECT * FROM id IN (?,?,?)'
252             #@BIND: (1, 2, 3)
253              
254             =item Named placeholder
255              
256             select_(one|row|all) and query methods support named placeholder.
257              
258             $dbh->select_all('SELECT * FROM users WHERE id IN (:ids) AND status = :status', {
259             ids => [1,2,3],
260             status => 'active',
261             });
262             #SQL: 'SELECT * FROM users WHERE id IN (?,?,?) AND status = ?'
263             #@BIND: (1, 2, 3, 'active')
264              
265             =item Typed bind parameters
266              
267             DBIx::Sunny allows you to specify data types of bind parameters. If a bind parameter is L<SQL::Maker::SQLType> object, its value is passed as its type, otherwise it is passed as default type (VARCHAR).
268              
269             use SQL::Maker::SQLType qw/sql_type/;
270             use DBI qw/:sql_types/
271              
272             $dbh->query(
273             'INSERT INTO bin_table (bin_col) VALUES (?)',
274             sql_type(\"\xDE\xAD\xBE\xEF", SQL_BINARY)),
275             );
276              
277             =back
278              
279             =head1 ADDITIONAL METHODS
280              
281             =over 4
282              
283             =item C<< $col = $dbh->select_one($query, @bind); >>
284              
285             Shortcut for prepare, execute and fetchrow_arrayref->[0]
286              
287             =item C<< $row = $dbh->select_row($query, @bind); >>
288              
289             Shortcut for prepare, execute and fetchrow_hashref
290              
291             =item C<< $rows = $dbh->select_all($query, @bind); >>
292              
293             Shortcut for prepare, execute and C<< selectall_arrayref(.., { Slice => {} }, ..) >>
294              
295             =item C<< $model = $dbh->select_row_as($model_class, $query, @bind); >>
296              
297             Shortcut for C<< $model_class->new(%{ $dbh->select_row($query, @bind) }) >>;
298              
299             =item C<< $models = $dbh->select_all_as($model_class, $query, @bind); >>
300              
301             Shortcut for C<< [ map { $model_class->new(%$_) } @{ $dbh->select_all($query, @bind) } ]; >>
302              
303             =item C<< $dbh->query($query, @bind); >>
304              
305             Shortcut for prepare, execute.
306              
307             =back
308              
309             =head1 AUTHOR
310              
311             Masahiro Nagano E<lt>kazeburo KZBRKZBR@ gmail.comE<gt>
312              
313             =head1 SEE ALSO
314              
315             L<DBI>, L<Amon2::DBI>
316              
317             =head1 LICENSE
318              
319             Copyright (C) Masahiro Nagano
320              
321             This library is free software; you can redistribute it and/or modify
322             it under the same terms as Perl itself.
323              
324             =cut