File Coverage

blib/lib/ORMesque/DBIxSimpleHack.pm
Criterion Covered Total %
statement 18 20 90.0
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 24 26 92.3


line stmt bran cond sub pod time code
1 2     2   55 use 5.006;
  2         6  
  2         180  
2 2     2   9 use strict;
  2         2  
  2         54  
3 2     2   5289 use DBI;
  2         43921  
  2         159  
4 2     2   24 use Carp ();
  2         4  
  2         4550  
5            
6             $DBIx::Simple::VERSION = '1.32_MOD';
7             $Carp::Internal{$_} = 1
8             for qw(DBIx::Simple DBIx::Simple::Result DBIx::Simple::DeadObject);
9            
10             my $quoted = qr/(?:'[^']*'|"[^"]*")*/; # 'foo''bar' simply matches the (?:) twice
11             my $quoted_mysql = qr/(?:(?:[^\\']*(?:\\.[^\\']*)*)'|"(?:[^\\"]*(?:\\.[^\\"]*)*)")*/;
12            
13             my %statements; # "$db" => { "$st" => $st, ... }
14             my %old_statements; # "$db" => [ [ $query, $st ], ... ]
15             my %keep_statements; # "$db" => $int
16            
17             my $err_message = '%s no longer usable (because of %%s)';
18             my $err_cause = '%s at %s line %d';
19            
20             package #Hide from PAUSE
21             DBIx::Simple;
22            
23             ### private helper subs
24            
25             sub _dummy { bless \my $dummy, 'DBIx::Simple::Dummy' }
26             sub _swap {
27             my ($hash1, $hash2) = @_;
28             my $tempref = ref $hash1;
29             my $temphash = { %$hash1 };
30             %$hash1 = %$hash2;
31             bless $hash1, ref $hash2;
32             %$hash2 = %$temphash;
33             bless $hash2, $tempref;
34             }
35            
36             ### constructor
37            
38             sub connect {
39             my ($class, @arguments) = @_;
40             my $self = { lc_columns => 1, result_class => 'DBIx::Simple::Result' };
41             if (defined $arguments[0] and UNIVERSAL::isa($arguments[0], 'DBI::db')) {
42             $self->{dont_disconnect} = 1;
43             $self->{dbh} = shift @arguments;
44             Carp::carp("Additional arguments for $class->connect are ignored")
45             if @arguments;
46             } else {
47             $arguments[3]->{PrintError} = 0
48             unless defined $arguments[3] and defined $arguments[3]{PrintError};
49             $self->{dbh} = DBI->connect(@arguments);
50             }
51            
52             return undef unless $self->{dbh};
53            
54             $self->{dbd} = $self->{dbh}->{Driver}->{Name};
55             bless $self, $class;
56            
57             $statements{$self} = {};
58             $old_statements{$self} = [];
59             $keep_statements{$self} = 16;
60            
61             return $self;
62             }
63            
64             sub new {
65             my ($class) = shift;
66             $class->connect(@_);
67             }
68            
69             ### properties
70            
71             sub keep_statements : lvalue { $keep_statements{ $_[0] } }
72             sub lc_columns : lvalue { $_[0]->{lc_columns} }
73             sub result_class : lvalue { $_[0]->{result_class} }
74            
75             sub abstract : lvalue {
76             require SQL::Abstract::Limit;
77             $_[0]->{abstract}
78             ||= SQL::Abstract::Limit->new( limit_dialect => $_[0]->{dbh} );
79             }
80            
81             ### private methods
82            
83             # Replace (??) with (?, ?, ?, ...)
84             sub _replace_omniholder {
85             my ($self, $query, $binds) = @_;
86             return if $$query !~ /\(\?\?\)/;
87             my $omniholders = 0;
88             my $q = $self->{dbd} =~ /mysql/ ? $quoted_mysql : $quoted;
89             $$query =~ s[($q|\(\?\?\))] {
90             $1 eq '(??)'
91             ? do {
92             Carp::croak('There can be only one omniholder')
93             if $omniholders++;
94             '(' . join(', ', ('?') x @$binds) . ')'
95             }
96             : $1
97             }eg;
98             }
99            
100             # Invalidate and clean up
101             sub _die {
102             my ($self, $cause) = @_;
103            
104             defined and $_->_die($cause, 0)
105             for values %{ $statements{$self} },
106             map $$_[1], @{ $old_statements{$self} };
107             delete $statements{$self};
108             delete $old_statements{$self};
109             delete $keep_statements{$self};
110            
111             unless ($self->{dont_disconnect}) {
112             # Conditional, because destruction order is not guaranteed
113             # during global destruction.
114             # $self->{dbh}->disconnect() if defined $self->{dbh};
115            
116             # this thing seem to be breaking lots of shit and I don't know why,
117             # also I'd argue whether its even neccessary :\ so im commenting it out
118             # eval { $self->{dbh}->disconnect() if defined $self->{dbh} };
119             }
120            
121             _swap(
122             $self,
123             bless {
124             what => 'Database object',
125             cause => $cause
126             }, 'DBIx::Simple::DeadObject'
127             ) unless $cause =~ /DESTROY/; # Let's not cause infinite loops :)
128             }
129            
130             ### public methods
131            
132             sub query {
133             my ($self, $query, @binds) = @_;
134             $self->{success} = 0;
135            
136             $self->_replace_omniholder(\$query, \@binds);
137            
138             my $st;
139             my $sth;
140            
141             my $old = $old_statements{$self};
142            
143             if (my $i = (grep $old->[$_][0] eq $query, 0..$#$old)[0]) {
144             $st = splice(@$old, $i, 1)->[1];
145             $sth = $st->{sth};
146             } else {
147             eval { $sth = $self->{dbh}->prepare($query) } or do {
148             if ($@) {
149             $@ =~ s/ at \S+ line \d+\.\n\z//;
150             Carp::croak($@);
151             }
152             $self->{reason} = "Prepare failed ($DBI::errstr)";
153             return _dummy;
154             };
155            
156             # $self is quoted on purpose, to pass along the stringified version,
157             # and avoid increasing reference count.
158             $st = bless {
159             db => "$self",
160             sth => $sth,
161             query => $query
162             }, 'DBIx::Simple::Statement';
163             $statements{$self}{$st} = $st;
164             }
165            
166             eval { $sth->execute(@binds) } or do {
167             if ($@) {
168             $@ =~ s/ at \S+ line \d+\.\n\z//;
169             Carp::croak($@);
170             }
171            
172             $self->{reason} = "Execute failed ($DBI::errstr)";
173             return _dummy;
174             };
175            
176             $self->{success} = 1;
177            
178             return bless { st => $st, lc_columns => $self->{lc_columns} }, $self->{result_class};
179             }
180            
181             sub error {
182             my ($self) = @_;
183             my $error = (ref $self ? $self->{dbh}->errstr : $DBI::errstr);
184             return 'DBI error: ' . ( $error || '' );
185             }
186            
187             sub dbh { $_[0]->{dbh} }
188             sub begin_work { $_[0]->{dbh}->begin_work }
189             sub begin { $_[0]->begin_work }
190             sub commit { $_[0]->{dbh}->commit }
191             sub rollback { $_[0]->{dbh}->rollback }
192             sub func { shift->{dbh}->func(@_) }
193            
194             sub last_insert_id {
195             my ($self) = @_;
196            
197             ($self->{dbi_version} ||= DBI->VERSION) >= 1.38 or Carp::croak(
198             "DBI v1.38 required for last_insert_id" .
199             "--this is only $self->{dbi_version}, stopped"
200             );
201            
202             return shift->{dbh}->last_insert_id(@_);
203             }
204            
205             sub disconnect {
206             my ($self) = @_;
207             $self->_die(sprintf($err_cause, "$self->disconnect", (caller)[1, 2]));
208             }
209            
210             sub DESTROY {
211             my ($self) = @_;
212             $self->_die(sprintf($err_cause, "$self->DESTROY", (caller)[1, 2]));
213             }
214            
215             ### public methods wrapping SQL::Abstract
216            
217             for my $method (qw/select insert update delete/) {
218 2     2   18 no strict 'refs';
  2         3  
  2         375  
219             *$method = sub {
220             my $self = shift;
221             return $self->query($self->abstract->$method(@_));
222             }
223             }
224            
225             ### public method wrapping SQL::Interp
226            
227             sub iquery {
228             require SQL::Interp;
229             my $self = shift;
230             return $self->query( SQL::Interp::sql_interp(@_) );
231             }
232            
233             package #nope
234             DBIx::Simple::Dummy;
235            
236             use overload
237 0           '""' => sub { shift },
238 2     2   4269 bool => sub { 0 };
  2         2294  
  2         22  
  0            
239            
240             sub new { bless \my $dummy, shift }
241             sub AUTOLOAD { return }
242            
243             package #nope
244             DBIx::Simple::DeadObject;
245            
246             sub _die {
247             my ($self) = @_;
248             Carp::croak(
249             sprintf(
250             "(This should NEVER happen!) " .
251             sprintf($err_message, $self->{what}),
252             $self->{cause}
253             )
254             );
255             }
256            
257             sub AUTOLOAD {
258             my ($self) = @_;
259             Carp::croak(
260             sprintf(
261             sprintf($err_message, $self->{what}),
262             $self->{cause}
263             )
264             );
265             }
266             sub DESTROY { }
267            
268             package #nope
269             DBIx::Simple::Statement;
270            
271             sub _die {
272             my ($self, $cause, $save) = @_;
273            
274             $self->{sth}->finish() if defined $self->{sth};
275             $self->{dead} = 1;
276            
277             my $stringy_db = "$self->{db}";
278             my $stringy_self = "$self";
279            
280             my $foo = bless {
281             what => 'Statement object',
282             cause => $cause
283             }, 'DBIx::Simple::DeadObject';
284            
285             DBIx::Simple::_swap($self, $foo);
286            
287             my $old = $old_statements{ $foo->{db} };
288             my $keep = $keep_statements{ $foo->{db} };
289            
290             if ($save and $keep) {
291             $foo->{dead} = 0;
292             shift @$old until @$old + 1 <= $keep;
293             push @$old, [ $foo->{query}, $foo ];
294             }
295            
296             delete $statements{ $stringy_db }{ $stringy_self };
297             }
298            
299             sub DESTROY {
300             # This better only happen during global destruction...
301             return if $_[0]->{dead};
302             $_[0]->_die('Ehm', 0);
303             }
304            
305             package #nope
306             DBIx::Simple::Result;
307            
308             sub _die {
309             my ($self, $cause) = @_;
310             if ($cause) {
311             $self->{st}->_die($cause, 1);
312             DBIx::Simple::_swap(
313             $self,
314             bless {
315             what => 'Result object',
316             cause => $cause,
317             }, 'DBIx::Simple::DeadObject'
318             );
319             } else {
320             $cause = $self->{st}->{cause};
321             DBIx::Simple::_swap(
322             $self,
323             bless {
324             what => 'Result object',
325             cause => $cause
326             }, 'DBIx::Simple::DeadObject'
327             );
328             Carp::croak(
329             sprintf(
330             sprintf($err_message, $self->{what}),
331             $cause
332             )
333             );
334             }
335             }
336            
337             sub func { shift->{st}->{sth}->func(@_) }
338             sub attr { my $dummy = $_[0]->{st}->{sth}->{$_[1]} }
339            
340             sub columns {
341             $_[0]->_die if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
342             my $c = $_[0]->{st}->{sth}->{ $_[0]->{lc_columns} ? 'NAME_lc' : 'NAME' };
343             return wantarray ? @$c : $c;
344             }
345            
346             sub bind {
347             $_[0]->_die if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
348             $_[0]->{st}->{sth}->bind_columns(\@_[1..$#_]);
349             }
350            
351             sub fetch {
352             $_[0]->_die if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
353             return $_[0]->{st}->{sth}->fetch;
354             }
355            
356             sub into {
357             $_[0]->_die if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
358             my $sth = $_[0]->{st}->{sth};
359             $sth->bind_columns(\@_[1..$#_]) if @_ > 1;
360             return $sth->fetch;
361             }
362            
363             sub list {
364             $_[0]->_die if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
365             return $_[0]->{st}->{sth}->fetchrow_array if wantarray;
366             return($_[0]->{st}->{sth}->fetchrow_array)[-1];
367             }
368            
369             sub array {
370             $_[0]->_die if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
371             my $row = $_[0]->{st}->{sth}->fetchrow_arrayref or return;
372             return [ @$row ];
373             }
374            
375             sub hash {
376             $_[0]->_die if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
377             return $_[0]->{st}->{sth}->fetchrow_hashref(
378             $_[0]->{lc_columns} ? 'NAME_lc' : 'NAME'
379             );
380             }
381            
382             sub flat {
383             $_[0]->_die if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
384             return map @$_, $_[0]->arrays if wantarray;
385             return [ map @$_, $_[0]->arrays ];
386             }
387            
388             sub arrays {
389             $_[0]->_die if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
390             return @{ $_[0]->{st}->{sth}->fetchall_arrayref } if wantarray;
391             return $_[0]->{st}->{sth}->fetchall_arrayref;
392             }
393            
394             sub hashes {
395             $_[0]->_die if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
396             my ($self) = @_;
397             my @return;
398             my $dummy;
399             push @return, $dummy while $dummy = $self->hash;
400             return wantarray ? @return : \@return;
401             }
402            
403             sub map_hashes {
404             $_[0]->_die if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
405             my ($self, $keyname) = @_;
406             Carp::croak('Key column name not optional') if not defined $keyname;
407             my @rows = $self->hashes;
408             my @keys;
409             push @keys, delete $_->{$keyname} for @rows;
410             my %return;
411             @return{@keys} = @rows;
412             return wantarray ? %return : \%return;
413             }
414            
415             sub map_arrays {
416             $_[0]->_die if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
417             my ($self, $keyindex) = @_;
418             $keyindex += 0;
419             my @rows = $self->arrays;
420             my @keys;
421             push @keys, splice @$_, $keyindex, 1 for @rows;
422             my %return;
423             @return{@keys} = @rows;
424             return wantarray ? %return : \%return;
425             }
426            
427             sub map {
428             $_[0]->_die if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
429             return map @$_, @{ $_[0]->{st}->{sth}->fetchall_arrayref } if wantarray;
430             return { map @$_, @{ $_[0]->{st}->{sth}->fetchall_arrayref } };
431             }
432            
433             sub rows {
434             $_[0]->_die if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
435             $_[0]->{st}->{sth}->rows;
436             }
437            
438             sub xto {
439             $_[0]->_die if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
440             require DBIx::XHTML_Table;
441             my $self = shift;
442             my $attr = ref $_[0] ? $_[0] : { @_ };
443            
444             # Old DBD::SQLite (.29) spits out garbage if done *after* fetching.
445             my $columns = $self->{st}->{sth}->{NAME};
446            
447             return DBIx::XHTML_Table->new(
448             scalar $self->arrays,
449             $columns,
450             $attr
451             );
452             }
453            
454             sub html {
455             $_[0]->_die if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
456             my $self = shift;
457             my $attr = ref $_[0] ? $_[0] : { @_ };
458             return $self->xto($attr)->output($attr);
459             }
460            
461             sub text {
462             $_[0]->_die if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
463             my ($self, $type) = @_;
464             my $text_table = defined $type && length $type
465             ? 0
466             : eval { require Text::Table; $type = 'table'; 1 };
467             $type ||= 'neat';
468             if ($type eq 'box' or $type eq 'table') {
469             my $box = $type eq 'box';
470             $text_table or require Text::Table;
471             my @columns = map +{ title => $_, align_title => 'center' },
472             @{ $self->{st}->{sth}->{NAME} };
473             my $c = 0;
474             splice @columns, $_ + $c++, 0, \' | ' for 1 .. $#columns;
475             my $table = Text::Table->new(
476             ($box ? \'| ' : ()),
477             @columns,
478             ($box ? \' |' : ())
479             );
480             $table->load($self->arrays);
481             my $rule = $table->rule(qw/- +/);
482             return join '',
483             ($box ? $rule : ()),
484             $table->title, $rule, $table->body,
485             ($box ? $rule : ());
486             }
487             Carp::carp("Unknown type '$type'; using 'neat'") if $type ne 'neat';
488             return join '', map DBI::neat_list($_) . "\n", $self->arrays;
489             }
490            
491             sub finish {
492             $_[0]->_die if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
493             my ($self) = @_;
494             $self->_die(
495             sprintf($err_cause, "$self->finish", (caller)[1, 2])
496             );
497             }
498            
499             sub DESTROY {
500             return if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
501             my ($self) = @_;
502             $self->_die(
503             sprintf($err_cause, "$self->DESTROY", (caller)[1, 2])
504             );
505             }
506              
507             package #Hide from PAUSE
508             ORMesque::DBIxSimpleHack;
509             1;
510             __END__