File Coverage

blib/lib/MySQL/Easy.pm
Criterion Covered Total %
statement 21 198 10.6
branch 0 104 0.0
condition 0 24 0.0
subroutine 7 37 18.9
pod 15 21 71.4
total 43 384 11.2


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