File Coverage

blib/lib/MySQL/Easy.pm
Criterion Covered Total %
statement 24 240 10.0
branch 0 116 0.0
condition 0 30 0.0
subroutine 8 40 20.0
pod 14 21 66.6
total 46 447 10.2


line stmt bran cond sub pod time code
1              
2             package MySQL::Easy::sth;
3              
4 9     9   30775 use Carp;
  9         15  
  9         505  
5 9     9   4499 use common::sense;
  9         64  
  9         32  
6              
7             our $AUTOLOAD;
8              
9             # new {{{
10             sub new {
11 0     0     my ($class, $mysql_e, $statement) = @_;
12 0           my $this = bless { s=>$statement, dbo=>$mysql_e }, $class;
13              
14 0           $this->{sth} = $this->{dbo}->handle->prepare( $statement );
15              
16 0           return $this;
17             }
18             # }}}
19             # bind_execute {{{
20             sub bind_execute {
21 0     0     my $this = shift;
22              
23 0           $this->{sth}->execute;
24 0           $this->{sth}->bind_columns( @_ );
25              
26 0           return $this;
27             }
28             # }}}
29             # {{{ sub repair_statement
30             sub repair_statement {
31 0     0     my $this = shift;
32              
33 0           $this->{sth} = $this->{dbo}->handle->prepare( $this->{s} );
34 0           return $this;
35             }
36              
37             # }}}
38             # AUTOLOAD {{{
39             sub AUTOLOAD {
40 0     0     my $_self = shift;
41 0           my $sub = $AUTOLOAD;
42 0 0         $sub = $1 if $sub =~ m/::(\w+)$/;
43              
44 0 0         return unless $_self->{sth}; # I should be dead?
45 0 0         croak "$sub is not a member of " . ref($_self->{sth}) unless $_self->{sth}->can($sub);
46              
47 0           *{ __PACKAGE__ . "::$sub" } = sub {
48 0     0     my $this = shift;
49 0           my $tries = 2;
50              
51 0 0         return unless $this->{sth}; # I should be dead?
52              
53 0           my $wa = wantarray;
54 0           my ($err, $warn, $ret, @ret);
55              
56             # warn "DEBUG: FYI, $$-$this is loading $sub()";
57              
58 0           EVAL_IT: eval {
59 9     9   3661 no strict 'refs';
  9         10  
  9         4308  
60 0           local $SIG{__WARN__} = sub { $warn = "@_"; };
  0            
61              
62 0 0         if( $wa ) {
63 0           @ret = $this->{sth}->$sub( @_ );
64              
65             } else {
66 0           $ret = $this->{sth}->$sub( @_ );
67             }
68             };
69              
70 0           $err = $@;
71              
72 0 0 0       if( $warn and not $err ) {
73 0           $err = $warn;
74 0           chomp $err;
75             }
76              
77 0 0         if( $err ) {
78             # my @c = caller;
79             # my $p = "at $c[1] line $c[2], prepared at $this->{_ready_caller}[1] line $this->{_ready_caller}[2]\n";
80 0           my $p = "(prepared at $this->{_ready_caller}[1] line $this->{_ready_caller}[2])";
81              
82 0           1 while $err =~ s/\s+at(?:\s+\S+)?\s+line\s+\d+\.?$//;
83              
84             # ERROR executing execute(): DBD::mysql::st execute failed: You have an error in your SQL syntax; check the manual
85 0           $err =~ s/DBD::mysql::sth? execute failed:\s*//;
86              
87 0 0         if( $err =~ m/(?:MySQL server has gone away|Lost connection)/ ) {
88 0 0         if( $sub eq "execute" ) {
89 0           $this->repair_statement;
90 0           $warn = undef;
91              
92 0 0         goto EVAL_IT if ((--$tries) > 0);
93              
94             } else {
95 0           croak "MySQL::Easy::sth can only recover from connection problems during execute(): $err $p";
96             }
97             }
98              
99 0           croak "ERROR executing $sub(): $err $p";
100             }
101              
102 0 0         return ($wa ? @ret : $ret);
103 0           };
104              
105 0           return $_self->$sub(@_);
106             }
107             # }}}
108             # DESTROY {{{
109             sub DESTROY {
110 0     0     my $this = shift;
111              
112             # warn "MySQL::Easy::sth is dying"; # This is here to make sure we don't normally die during global destruction.
113             # Once it appeared to function correctly, it was removed.
114             # Lastly, we would die during global dest iff: our circular ref from new() were not removed.
115             # Although, to be truely circular, the MySQL::Easy would need to point to this ::sth also
116             # and it probably doesn't. So, is this delete paranoid? Yeah... meh.
117 0           delete $this->{dbo};
118             }
119             # }}}
120              
121             package MySQL::Easy;
122              
123 9     9   41 use Carp ();
  9         12  
  9         191  
124 9     9   28 use common::sense;
  9         11  
  9         30  
125 9     9   288 use Scalar::Util qw(blessed);
  9         13  
  9         1106  
126 9     9   8353 use overload fallback=>1, '""' => sub { ref($_[0]) . "($_[0]{dbase})" };
  9     0   6891  
  9         69  
  0         0  
127              
128 9     9   13826 use DBI;
  9         128532  
  9         24782  
129              
130             our $AUTOLOAD;
131             our $VERSION = "2.1019";
132             our $CNF_ENV = "ME_CNF";
133             our $USER_ENV = "ME_USER";
134             our $PASS_ENV = "ME_PASS";
135             our $HOME_ENV = "HOME";
136             our @MY_CNF_LOCATIONS = (
137             $ENV{$CNF_ENV}, "$ENV{$HOME_ENV}/.my.cnf", "/etc/mysql-easy.cnf", "/etc/mysql/my.cnf"
138             );
139              
140             # {{{ sub mycroak
141             sub mycroak(;$) {
142 0     0 0   my $error = shift;
143              
144 0           my $i = 1;
145 0           my @c = caller( $i);
146 0           @c = caller(++$i) while $c[0] eq __PACKAGE__;
147              
148 0           chomp $error;
149              
150 0           1 while
151             $error =~ s{\s+at\s+\S+\s+line\s+\d+\.}{}g;
152 0           $error =~ s{\s+\(prepared at $c[1] line $c[2]\)}{}; # this would be a dup error in this case
153              
154             #arn "<<<$error\[$i]>>>";
155              
156 0           Carp::croak($error);
157             }
158              
159             # }}}
160              
161             # AUTOLOAD {{{
162             sub AUTOLOAD {
163 0     0     my $_self = shift;
164 0           my $sub = $AUTOLOAD;
165 0 0         $sub = $1 if $sub =~ m/::(\w+)$/;
166              
167             {
168 0           my $handle = $_self->handle;
  0            
169 0 0         mycroak "$sub is not a member of " . ref($handle) unless $handle->can($sub);
170             }
171              
172 0           *{ __PACKAGE__ . "::$sub" } = sub {
173 0     0     my $this = shift;
174 0           my $handle = $this->handle;
175              
176 0           my $wa = wantarray;
177 0           my ($err, $warn, $ret, @ret);
178 0           my @oargs = @_;
179 0           my $tries = 2;
180              
181 0           EVAL_IT: my $eval_result = eval {
182 0           my @cargs = @oargs;
183 0           local $SIG{__WARN__} = sub { $warn = "@_"; };
  0            
184              
185 0 0 0       $cargs[0] = $cargs[0]->{sth} if @cargs and blessed $cargs[0] and $cargs[0]->isa("MySQL::Easy::sth");
      0        
186              
187 0 0         if( wantarray ) {
188 0           @ret = $handle->$sub(@cargs);
189              
190             } else {
191 0           $ret = $handle->$sub(@cargs);
192             }
193              
194 0           !$warn
195             };
196              
197 0 0         unless( $eval_result ) {
198 0           $err = $@;
199              
200 0 0         if( $err =~ m/(?:MySQL server has gone away|Lost connection)/ ) {
201 0 0         if( blessed $oargs[0] ) {
202 0 0         if( $oargs[0]->isa("MySQL::Easy::sth") ) {
203 0           $oargs[0]->repair_statement;
204              
205             } else {
206 0           warn "argument to $sub is blessed, but is not a MySQL::Easy::sth, connection rebuild will probably fail";
207             }
208             }
209 0 0         goto EVAL_IT if ((--$tries) > 0);
210             }
211              
212 0 0 0       if( $warn and not $err ) {
213 0           $err = $warn;
214 0           chomp $err;
215             }
216              
217 0           $err =~ s/DBD::mysql::dbh? \S+ failed:\s*//;
218 0           mycroak "ERROR executing $sub(): $err";
219             }
220              
221 0 0         return ($wa ? @ret : $ret);
222 0           };
223              
224             #arn "created method $sub, calling";
225              
226 0           return $_self->$sub(@_);
227             }
228             # }}}
229              
230             # check_warnings {{{
231             sub check_warnings {
232 0     0 1   my $this = shift;
233 0           my $sth = $this->ready("show warnings");
234              
235             # mysql> show warnings;
236             # +---------+------+------------------------------------------+
237             # | Level | Code | Message |
238             # +---------+------+------------------------------------------+
239             # | Warning | 1265 | Data truncated for column 'var' at row 1 |
240             # +---------+------+------------------------------------------+
241              
242 0           my @warnings;
243              
244 0 0         execute $sth or die $this->errstr;
245 0           while( my $a = fetchrow_arrayref $sth ) {
246 0           push @warnings, $a;
247             }
248 0           finish $sth;
249              
250 0 0         if( @warnings ) {
251 0           $@ = join("\n", map("$_->[0]($_->[1]): $_->[2]", @warnings)) . "\n";
252              
253 0           return 0;
254             }
255              
256 0           return 1;
257             }
258             # }}}
259             # new {{{
260             sub new {
261 0     0 1   my $this = shift;
262              
263 0           $this = bless {}, $this;
264              
265 0 0         $this->{dbase} = shift; mycroak "dbase = '$this->{dbase}'?" unless $this->{dbase};
  0            
266 0 0 0       $this->{dbh} = $this->{dbase} if ref($this->{dbase}) and $this->{dbase}->isa("DBI::db");
267              
268 0           my $args = shift;
269 0 0         my $tr = ref($args) ? delete $args->{trace} : $args;
270              
271 0 0 0       $this->trace($tr) if $tr and $this->{dbh};
272              
273 0 0         if( ref $args ) {
274 0           for my $k (keys %$args) {
275 0           my $f;
276              
277 0 0         if( $this->can($f = "set_$k") ) {
278 0           $this->$f($args->{$k});
279              
280             } else {
281 0           mycroak "unrecognized attribute: $k"
282             }
283             }
284             }
285              
286 0           return $this;
287             }
288             # }}}
289             # do {{{
290             sub do {
291 0 0   0 1   my $this = shift; return unless @_;
  0            
292 0           my $sql = shift;
293 0 0         my $r; eval { $r = $this->ready($sql)->execute(@_); 1 } or mycroak $@;
  0            
  0            
  0            
294 0           return $r;
295             }
296             # }}}
297             # light_lock {{{
298             sub light_lock {
299 0 0   0 0   my $this = shift; return unless @_;
  0            
300 0           my $tolock = join(", ", map("$_ read", @_));
301              
302 0           $this->do("lock tables $tolock");
303             }
304             # }}}
305             # lock {{{
306             sub lock {
307 0 0   0 1   my $this = shift; return unless @_;
  0            
308 0           my $tolock = join(", ", map("$_ write", @_));
309              
310 0           $this->do("lock tables $tolock");
311             }
312             # }}}
313             # unlock {{{
314             sub unlock {
315 0     0 0   my $this = shift;
316              
317 0           $this->do("unlock tables");
318             }
319             # }}}
320             # ready {{{
321             sub ready {
322 0     0 1   my $this = shift;
323              
324 0           my $i = 0;
325 0           my $sth = MySQL::Easy::sth->new( $this, @_ );
326 0           $sth->{_ready_caller} = [ caller( $i) ];
327 0           $sth->{_ready_caller} = [ caller(++$i) ] while $sth->{_ready_caller}[0] eq __PACKAGE__;
328              
329 0           return $sth;
330             }
331             # }}}
332             # firstcol {{{
333             sub firstcol {
334 0     0 1   my $this = shift;
335 0           my $query = shift;
336              
337 0           my $r;
338 0 0         eval { $r = $this->selectcol_arrayref($query, undef, @_); 1 }
  0            
  0            
339             or mycroak $@;
340              
341 0 0         return wantarray ? @$r : $r;
342             }
343             # }}}
344             # firstval {{{
345             sub firstval {
346 0     0 1   my $this = shift;
347 0           my $query = shift;
348              
349 0           my $r;
350 0 0         eval { $r = $this->selectcol_arrayref($query, undef, @_); 1 }
  0            
  0            
351             or mycroak $@;
352              
353 0           return $r->[0];
354             }
355             # }}}
356             # firstrow {{{
357             sub firstrow {
358 0     0 1   my $this = shift;
359 0           my $query = shift;
360              
361 0           my $r;
362 0 0         eval { $r = $this->selectrow_arrayref($query, undef, @_); 1 }
  0            
  0            
363             or mycroak $@;
364              
365 0 0         return wantarray ? @$r : $r;
366             }
367             # }}}
368             # thread_id {{{
369             sub thread_id {
370 0     0 0   my $this = shift;
371              
372 0           return $this->handle->{mysql_thread_id};
373             }
374             # }}}
375             # last_insert_id {{{
376             sub last_insert_id {
377 0     0 1   my $this = shift;
378              
379             # return $this->firstcol("select last_insert_id()")->[0];
380             # return $this->handle->{mysql_insertid};
381 0           return $this->handle->last_insert_id(undef,undef,undef,undef);
382             }
383             # }}}
384             # DESTROY {{{
385             sub DESTROY {
386 0     0     my $this = shift;
387              
388 0 0         $this->{dbh}->disconnect if $this->{dbh};
389             }
390             # }}}
391             # handle {{{
392             sub handle {
393 0     0 0   my $this = shift;
394              
395 0 0 0       return $this->{dbh} if defined($this->{dbh}) and $this->{dbh}->ping;
396             # warn "WARNING: MySQL::Easy is trying to reconnect (if possible)" if defined $this->{dbh};
397              
398 0 0 0       ($this->{user}, $this->{pass}) = $this->unp unless $this->{user} and $this->{pass};
399              
400 0 0         $this->{host} = "localhost" unless $this->{host};
401 0 0         $this->{port} = "3306" unless $this->{port};
402 0 0         $this->{dbase} = "test" unless $this->{dbase};
403 0 0         $this->{trace} = 0 unless $this->{trace};
404              
405 0 0         if( $this->{dbh} ) {
406 0           eval {
407 0     0     local $SIG{__WARN__} = sub {}; # Curiously, sometimes we do have a handle, but the ping doesn't work.
  0            
408             # If we replace the handle, DBI complains about not disconnecting.
409             # If we disconnect, it complains about not desting statement handles.
410             # Heh. It's gone dude, let it go.
411 0           $this->{dbh}->disconnect;
412             };
413             }
414              
415 0 0         $this->{dbh} =
    0          
416             DBI->connect("DBI:mysql:$this->{dbase}:host=$this->{host}:port=$this->{port}",
417             $this->{user}, $this->{pass}, {
418              
419             RaiseError => ($this->{raise} ? 1:0),
420             PrintError => ($this->{raise} ? 0:1),
421              
422             AutoCommit => 0,
423              
424             mysql_enable_utf8 => 1,
425             mysql_compression => 1,
426             mysql_ssl => 1,
427             mysql_auto_reconnect => 1,
428              
429             });
430              
431 0 0         mycroak "failed to generate connection: " . DBI->errstr unless $this->{dbh};
432              
433 0           $this->{dbh}->trace($this->{trace});
434              
435 0           return $this->{dbh};
436             }
437             # }}}
438             # unp {{{
439             sub unp {
440 0     0 0   my $this = shift;
441              
442 0 0 0       return ($ENV{$USER_ENV}, $ENV{$PASS_ENV}) if $ENV{$USER_ENV} and $ENV{$PASS_ENV};
443              
444 0           my ($user, $pass, $file, $fh);
445              
446 0           for $file (@MY_CNF_LOCATIONS) {
447 0 0         next unless -f $file;
448 0 0         next unless open $fh, $file;
449              
450 0           while(<$fh>) {
451 0 0         $user = $1 if m/user\s*=\s*(.+)/;
452 0 0         $pass = $1 if m/password\s*=\s*(.+)/;
453              
454 0 0 0       return ($user, $pass) if $user and $pass;
455             }
456             }
457              
458 0           die "unable to locate a username and password\n";
459 0           return;
460             }
461             # }}}
462             # set_host set_user set_pass {{{
463             sub set_host {
464 0     0 1   my $this = shift;
465              
466 0           $this->{host} = shift;
467             }
468              
469             sub set_port {
470 0     0 1   my $this = shift;
471              
472 0           $this->{port} = shift;
473             }
474              
475             sub set_user {
476 0     0 1   my $this = shift;
477              
478 0           $this->{user} = shift;
479             }
480              
481             sub set_pass {
482 0     0 1   my $this = shift;
483              
484 0           $this->{pass} = shift;
485             }
486              
487             sub set_raise {
488 0     0 0   my $this = shift;
489              
490 0           $this->{raise} = shift;
491             }
492             # }}}
493             # bind_execute {{{
494             sub bind_execute {
495 0     0 1   my $this = shift;
496 0           my $sql = shift;
497              
498 0           my $sth = $this->ready($sql);
499              
500 0 0         $sth->execute or return;
501 0 0         $sth->bind_columns( @_ ) or return;
502              
503 0           return $sth;
504             }
505             # }}}
506              
507             1;