File Coverage

blib/lib/DBIx/ThinSQL.pm
Criterion Covered Total %
statement 359 467 76.8
branch 118 172 68.6
condition 9 26 34.6
subroutine 58 94 61.7
pod 6 25 24.0
total 550 784 70.1


line stmt bran cond sub pod time code
1             package DBIx::ThinSQL;
2 3     3   47747 use strict;
  3         4  
  3         62  
3 3     3   9 use warnings;
  3         3  
  3         58  
4 3     3   3909 use DBI;
  3         34264  
  3         184  
5             use Exporter::Tidy
6 3         20 other => [qw/ bv qv qi sq func OR AND /],
7             sql => [
8             qw/
9             case
10             cast
11             coalesce
12             concat
13             count
14             exists
15             hex
16             length
17             lower
18             ltrim
19             max
20             min
21             replace
22             rtrim
23             substr
24             sum
25             upper
26             /
27 3     3   2548 ];
  3         23  
28              
29             our @ISA = 'DBI';
30             our $VERSION = '0.0.45_2';
31              
32             sub ejoin {
33 0     0 0 0 my $joiner = shift;
34 0 0       0 return unless @_;
35              
36 0         0 my @tokens = map { $_, $joiner } @_;
  0         0  
37 0         0 pop @tokens;
38              
39 0         0 return @tokens;
40             }
41              
42             sub func {
43 0     0 1 0 my $func = uc shift;
44 0         0 my $joiner = shift;
45              
46 0         0 return DBIx::ThinSQL::expr->new( $func, '(',
47             DBIx::ThinSQL::ejoin( $joiner, @_ ), ')' );
48             }
49              
50 7     7 1 2647698 sub bv { DBIx::ThinSQL::expr->new( DBIx::ThinSQL::bind_value->new(@_) ) }
51              
52 3     3 1 1410 sub qv { DBIx::ThinSQL::expr->new( DBIx::ThinSQL::quote_value->new(@_) ) }
53              
54 0     0 0 0 sub qi { DBIx::ThinSQL::expr->new( DBIx::ThinSQL::quote_identifier->new(@_) ) }
55              
56 0     0 1 0 sub OR { ' OR ' }
57              
58 0     0 1 0 sub AND { ' AND ' }
59              
60 0     0 0 0 sub cast { func( 'cast', ' ', @_ ) }
61              
62 0     0 0 0 sub case { DBIx::ThinSQL::case->new(@_) }
63              
64 0     0 0 0 sub coalesce { func( 'coalesce', ', ', @_ ) }
65              
66 0     0 0 0 sub concat { DBIx::ThinSQL::expr->new( DBIx::ThinSQL::ejoin( ' || ', @_ ) ) }
67              
68 0     0 0 0 sub count { func( 'count', ', ', @_ ) }
69              
70 0     0 0 0 sub exists { func( 'exists', ', ', @_ ) }
71              
72 0     0 0 0 sub hex { func( 'hex', ', ', @_ ) }
73              
74 0     0 0 0 sub length { func( 'length', ', ', @_ ) }
75              
76 0     0 0 0 sub lower { func( 'lower', ', ', @_ ) }
77              
78 0     0 0 0 sub ltrim { func( 'ltrim', ', ', @_ ) }
79              
80 0     0 0 0 sub max { func( 'max', ', ', @_ ) }
81              
82 0     0 0 0 sub min { func( 'min', ', ', @_ ) }
83              
84 0     0 0 0 sub replace { func( 'replace', ', ', @_ ) }
85              
86 0     0 0 0 sub rtrim { func( 'rtrim', ', ', @_ ) }
87              
88 0     0 0 0 sub substr { func( 'substr', ', ', @_ ) }
89              
90 1     1 1 967 sub sq { DBIx::ThinSQL::query->new(@_) }
91              
92 0     0 0 0 sub sum { func( 'sum', '', @_ ) }
93              
94 0     0 0 0 sub upper { func( 'upper', ', ', @_ ) }
95              
96             package DBIx::ThinSQL::db;
97 3     3   1375 use strict;
  3         3  
  3         62  
98 3     3   10 use warnings;
  3         2  
  3         76  
99 3     3   9 use Carp ();
  3         3  
  3         40  
100 3     3   1102 use Log::Any '$log';
  3         29872  
  3         11  
101 3     3   9402 use DBIx::ThinSQL::Driver;
  3         5  
  3         4635  
102              
103             our @ISA = qw(DBI::db);
104             our @CARP_NOT;
105              
106             sub share_dir {
107 35     35   238 require Path::Tiny;
108              
109 35 50       82 return Path::Tiny::path($DBIX::ThinSQL::SHARE_DIR)
110             if defined $DBIX::ThinSQL::SHARE_DIR;
111              
112 35         87 require File::ShareDir;
113 35         141 return Path::Tiny::path( File::ShareDir::dist_dir('DBIx-ThinSQL') );
114             }
115              
116             sub throw_error {
117 2     2   4 my $self = shift;
118 2         417 Carp::croak(@_);
119             }
120              
121             sub sql_bv {
122 225     225   182 my $self = shift;
123 225         139 my $sql = shift;
124 225         120 my $bv = shift;
125 225         166 my $val = shift;
126 225         129 my $prefix = shift;
127              
128 225 100       288 $prefix = '' unless length($prefix);
129              
130 225         204 my $ref = ref $val;
131 225         191 my $prefix2 = $prefix . ' ';
132              
133             # When we call ourself we already have a ref
134              
135 225 100       456 if ( $ref eq '' ) {
    100          
    100          
    100          
    100          
    50          
    100          
    100          
    100          
    50          
136 110 50       194 $$sql .= defined $val ? $val : 'NULL';
137             }
138             elsif ( $ref eq 'DBIx::ThinSQL::query' ) {
139 34 100       54 my $bracket = length($prefix) ? '(' : '';
140 34         54 foreach my $pair ( $val->tokens ) {
141 89 50       165 $$sql .= "\n" if $pair->[0] =~ /UNION/;
142 89 50 33     419 my $join_on = length( $pair->[0] )
143             && ( $pair->[0] =~ m/(JOIN)|(ON)/ ) ? ' ' : '';
144 89 50 66     355 $$sql .=
145             ( $bracket || $prefix . $join_on ) . $pair->[0] . "\n" . $prefix2
146             if length( $pair->[0] );
147 89         123 $self->sql_bv( $sql, $bv, $pair->[1], $prefix2 );
148 89 50 33     260 $$sql .= "\n" if length( $pair->[0] ) or length( $pair->[1] );
149 89         88 $bracket = '';
150             }
151 34 100       83 $$sql .= $prefix . ")" if length($prefix);
152             }
153             elsif ( $ref eq 'DBIx::ThinSQL::list' ) {
154 21         29 my @tokens = $val->tokens;
155 21         26 my $last = pop @tokens;
156 21         22 my $i = 0;
157 21         25 foreach my $token (@tokens) {
158 19 50       28 $$sql .= $prefix if $i++;
159 19         25 $self->sql_bv( $sql, $bv, $token, $prefix );
160 19         34 $$sql .= ",\n";
161             }
162 21         20 $$sql .= $prefix;
163 21         28 $self->sql_bv( $sql, $bv, $last, $prefix );
164             }
165             elsif ( $ref eq 'DBIx::ThinSQL::table' ) {
166 4         13 my @tokens = $val->tokens;
167 4         5 my $table = shift @tokens;
168 4         10 $$sql .= $table . "(\n";
169              
170 4         5 my $last = pop @tokens;
171 4         7 foreach my $token (@tokens) {
172 4         4 $$sql .= $prefix2;
173 4         8 $self->sql_bv( $sql, $bv, $token, $prefix2 );
174 4         5 $$sql .= ",\n";
175             }
176 4         5 $$sql .= $prefix2;
177 4         7 $self->sql_bv( $sql, $bv, $last, $prefix2 );
178 4         9 $$sql .= "\n" . $prefix . ")";
179             }
180             elsif ( $ref eq 'DBIx::ThinSQL::values' ) {
181 8         20 my @tokens = $val->tokens;
182 8         13 $$sql .= "(\n";
183              
184 8         12 my $last = pop @tokens;
185 8         14 foreach my $token (@tokens) {
186 8         12 $$sql .= $prefix2;
187 8         20 $self->sql_bv( $sql, $bv, $token, $prefix2 );
188 8         13 $$sql .= ",\n";
189             }
190 8         15 $$sql .= $prefix2;
191 8         15 $self->sql_bv( $sql, $bv, $last, $prefix2 );
192 8         17 $$sql .= "\n" . $prefix . ")";
193             }
194             elsif ( $ref eq 'DBIx::ThinSQL::case' ) {
195 0         0 $$sql .= "CASE\n";
196 0         0 my @tokens = $val->tokens;
197 0         0 foreach my $pair (@$val) {
198 0         0 $$sql .= $prefix2 . $pair->[0] . "\n" . $prefix2 . ' ';
199 0         0 $self->sql_bv( $sql, $bv, $pair->[1], $prefix2 );
200 0         0 $$sql .= "\n";
201             }
202 0         0 $$sql .= $prefix . "END";
203             }
204             elsif ( $ref eq 'DBIx::ThinSQL::expr' ) {
205 19         28 foreach my $token ( $val->tokens ) {
206 39         49 $self->sql_bv( $sql, $bv, $token, $prefix );
207             }
208             }
209             elsif ( $ref eq 'DBIx::ThinSQL::bind_value' ) {
210 25         24 $$sql .= '?';
211 25         19 push( @{$bv}, $val );
  25         51  
212             }
213             elsif ( $ref eq 'DBIx::ThinSQL::quote_value' ) {
214 3         9 $$sql .= $self->quote( $val->for_quote );
215             }
216             elsif ( $ref eq 'DBIx::ThinSQL::quote_identifier' ) {
217 1         4 $$sql .= $self->quote_identifier( $val->val );
218             }
219             else {
220 0         0 Carp::cluck "sql_bv doesn't know $ref";
221             }
222             }
223              
224             sub query {
225 33     33   36 my $self = shift;
226 33         52 my ( $sql, @bv ) = ('');
227 33         102 $self->sql_bv( \$sql, \@bv, DBIx::ThinSQL::query->new(@_) );
228 33         169 return $sql . ";\n", @bv;
229             }
230              
231             sub xprepare {
232 20     20   23 my $self = shift;
233 20 50       41 Carp::croak('xprepare requires arguments!') unless @_;
234              
235 20         35 my ( $sql, @bv ) = $self->query(@_);
236              
237             # TODO these locals have no effect?
238 20         145 local $self->{RaiseError} = 1;
239 20         280 local $self->{PrintError} = 0;
240 20         172 local $self->{ShowErrorStatement} = 1;
241              
242 20         138 my $prepare_ok;
243 20         16 my $sth = eval {
244 20         70 my $sth = $self->prepare($sql);
245 20         1417 $prepare_ok = 1;
246              
247 20         18 my $i = 1;
248 20         36 foreach my $bv (@bv) {
249 2         7 $sth->bind_param( $i++, $bv->for_bind_param );
250             }
251              
252 20         27 $sth;
253             };
254              
255 20 50       38 if ($@) {
256 0 0       0 $log->debug($sql) unless $prepare_ok;
257 0         0 $self->throw_error($@);
258             }
259              
260 20         231 return $sth;
261             }
262              
263             sub xdo {
264 13     13   2522 my $self = shift;
265 13         36 my ( $sql, @bv ) = $self->query(@_);
266              
267             # TODO these locals have no effect?
268 13         122 local $self->{RaiseError} = 1;
269 13         212 local $self->{PrintError} = 0;
270 13         110 local $self->{ShowErrorStatement} = 1;
271              
272 13 100       119 return $self->do($sql) unless @bv;
273              
274 12         13 my $sth = eval {
275 12         56 my $sth = $self->prepare($sql);
276 12         895 my $i = 1;
277 12         23 foreach my $bv (@bv) {
278 23         51 $sth->bind_param( $i++, $bv->for_bind_param );
279             }
280              
281 12         19 $sth;
282             };
283              
284 12 50       27 $self->throw_error($@) if $@;
285              
286 12         166172 return $sth->execute;
287             }
288              
289             sub log_debug {
290 0     0   0 my $self = shift;
291 0         0 my $sql = (shift) . "\n";
292              
293 0         0 my $sth = $self->prepare( $sql . ';' );
294 0         0 $sth->execute(@_);
295              
296 0         0 my $out = join( ', ', @{ $sth->{NAME} } ) . "\n";
  0         0  
297 0         0 $out .= ' ' . ( '-' x length $out ) . "\n";
298 0         0 $out .= ' ' . DBI::neat_list($_) . "\n" for @{ $sth->fetchall_arrayref };
  0         0  
299 0         0 $log->debug($out);
300             }
301              
302             sub log_warn {
303 0     0   0 my $self = shift;
304 0         0 my $sql = (shift) . "\n";
305              
306 0         0 my $sth = $self->prepare( $sql . ';' );
307 0         0 $sth->execute(@_);
308              
309 0         0 my $out = join( ', ', @{ $sth->{NAME} } ) . "\n";
  0         0  
310 0         0 $out .= ' ' . ( '-' x length $out ) . "\n";
311 0         0 $out .= ' ' . DBI::neat_list($_) . "\n" for @{ $sth->fetchall_arrayref };
  0         0  
312 0         0 warn $out;
313             }
314              
315             sub dump {
316 0     0   0 my $self = shift;
317 0         0 my $sth = $self->prepare(shift);
318 0         0 $sth->execute(@_);
319 0         0 $sth->dump_results;
320             }
321              
322             sub xdump {
323 0     0   0 my $self = shift;
324 0         0 my $sth = $self->xprepare(@_);
325 0         0 $sth->execute;
326 0         0 $sth->dump_results;
327             }
328              
329             sub xval {
330 3     3   1260 my $self = shift;
331              
332 3         10 my $sth = $self->xprepare(@_);
333 3         138 $sth->execute;
334 3         14 my $ref = $sth->arrayref;
335 3         28 $sth->finish;
336              
337 3 50       44 return $ref->[0] if $ref;
338 0         0 return;
339             }
340              
341             sub xvals {
342 0     0   0 my $self = shift;
343 0         0 my $sth = $self->xprepare(@_);
344 0         0 $sth->execute;
345 0         0 return $sth->vals;
346             }
347              
348             sub xlist {
349 2     2   2964 my $self = shift;
350              
351 2         5 my $sth = $self->xprepare(@_);
352 2         83 $sth->execute;
353 2         5 my $ref = $sth->arrayref;
354 2         10 $sth->finish;
355              
356 2 100       16 return @$ref if $ref;
357 1         10 return;
358             }
359              
360             sub xarrayref {
361 2     2   2332 my $self = shift;
362              
363 2         6 my $sth = $self->xprepare(@_);
364 2         78 $sth->execute;
365 2         5 my $ref = $sth->arrayref;
366 2         10 $sth->finish;
367              
368 2 100       14 return $ref if $ref;
369 1         9 return;
370             }
371              
372             sub xarrayrefs {
373 7     7   4147 my $self = shift;
374              
375 7         19 my $sth = $self->xprepare(@_);
376 7         262 $sth->execute;
377              
378 7         21 return $sth->arrayrefs;
379             }
380              
381             sub xhashref {
382 2     2   2292 my $self = shift;
383              
384 2         3 my $sth = $self->xprepare(@_);
385 2         98 $sth->execute;
386 2         8 my $ref = $sth->hashref;
387 2         15 $sth->finish;
388              
389 2 100       14 return $ref if $ref;
390 1         10 return;
391             }
392              
393             sub xhashrefs {
394 4     4   3457 my $self = shift;
395              
396 4         9 my $sth = $self->xprepare(@_);
397 4         182 $sth->execute;
398 4         11 return $sth->hashrefs;
399             }
400              
401             # Can't use 'local' to managed txn count here because $self is a tied hashref?
402             # Also can't use ||=.
403             sub txn {
404 6     6   8057 my $self = shift;
405 6         7 my $subref = shift;
406 6         7 my $wantarray = wantarray;
407 6         53 my $txn = $self->{private_DBIx_ThinSQL_txn}++;
408 6         19 my $driver = $self->{private_DBIx_ThinSQL_driver};
409              
410 6   66     17 $driver ||= $self->{private_DBIx_ThinSQL_driver} = do {
411 1         15 my $class = 'DBIx::ThinSQL::Driver::' . $self->{Driver}->{Name};
412 1         27 ( my $path = $class ) =~ s{::}{/}g;
413 1         1 $path .= '.pm';
414              
415 1 50       2 eval { require $path; $class->new } || DBIx::ThinSQL::Driver->new;
  1         515  
  1         9  
416             };
417              
418 6         8 my $current;
419 6 100       10 if ( !$txn ) {
420             $current = {
421             RaiseError => $self->{RaiseError},
422             ShowErrorStatement => $self->{ShowErrorStatement},
423 4         34 };
424              
425             }
426              
427 6 50       24 $self->{RaiseError} = 1 unless exists $self->{HandleError};
428 6         65 $self->{ShowErrorStatement} = 1;
429              
430 6         10 my @result;
431             my $result;
432              
433 6 100       13 if ( !$txn ) {
434 4         42 $self->begin_work;
435             }
436             else {
437 2         11 $driver->savepoint( $self, 'txn' . $txn );
438             }
439              
440 6         117 eval {
441              
442 6 100       12 if ($wantarray) {
443 1         3 @result = $subref->();
444             }
445             else {
446 5         8 $result = $subref->();
447             }
448              
449 4 100       336 if ( !$txn ) {
450              
451             # We check again for the AutoCommit state in case the
452             # $subref did something like its own ->rollback(). This
453             # really just prevents a warning from being printed.
454 3 50       5349 $self->commit unless $self->{AutoCommit};
455             }
456             else {
457             $driver->release( $self, 'txn' . $txn )
458 1 50       10 unless $self->{AutoCommit};
459             }
460              
461             };
462 6         66076 my $error = $@;
463              
464 6         62 $self->{private_DBIx_ThinSQL_txn} = $txn;
465 6 100       18 if ( !$txn ) {
466 4         13 $self->{RaiseError} = $current->{RaiseError};
467 4         15 $self->{ShowErrorStatement} = $current->{ShowErrorStatement};
468             }
469              
470 6 100       14 if ($error) {
471              
472 2         3 eval {
473 2 100       8 if ( !$txn ) {
474              
475             # If the transaction failed at COMMIT, then we can no
476             # longer roll back. Maybe put this around the eval for
477             # the RELEASE case as well??
478 1 50       12 $self->rollback unless $self->{AutoCommit};
479             }
480             else {
481             $driver->rollback_to( $self, 'txn' . $txn )
482 1 50       12 unless $self->{AutoCommit};
483             }
484             };
485              
486 2 50       25 $self->throw_error(
487             $error . "\nAdditionally, an error occured during
488             rollback:\n$@"
489             ) if $@;
490              
491 2         7 $self->throw_error($error);
492             }
493              
494 4 100       21 return $wantarray ? @result : $result;
495             }
496              
497             package DBIx::ThinSQL::st;
498 3     3   14 use strict;
  3         3  
  3         50  
499 3     3   8 use warnings;
  3         3  
  3         775  
500              
501             our @ISA = qw(DBI::st);
502              
503             sub val {
504 0     0   0 my $self = shift;
505 0   0     0 my $ref = $self->fetchrow_arrayref || return;
506 0         0 return $ref->[0];
507             }
508              
509             sub vals {
510 0     0   0 my $self = shift;
511 0   0     0 my $all = $self->fetchall_arrayref || return;
512 0 0       0 return unless @$all;
513 0 0       0 return map { $_->[0] } @$all if wantarray;
  0         0  
514 0         0 return [ map { $_->[0] } @$all ];
  0         0  
515             }
516              
517             sub list {
518 0     0   0 my $self = shift;
519 0   0     0 my $ref = $self->fetchrow_arrayref || return;
520 0         0 return @$ref;
521             }
522              
523             sub arrayref {
524 7     7   8 my $self = shift;
525 7 50       63 return unless $self->{Active};
526 7         68 return $self->fetchrow_arrayref;
527             }
528              
529             sub arrayrefs {
530 7     7   10 my $self = shift;
531 7 50       52 return unless $self->{Active};
532              
533 7   50     97 my $all = $self->fetchall_arrayref || return;
534 7 100       34 return unless @$all;
535 5 100       29 return @$all if wantarray;
536 3         30 return $all;
537             }
538              
539             sub hashref {
540 2     2   3 my $self = shift;
541 2 50       16 return unless $self->{Active};
542              
543 2         53 return $self->fetchrow_hashref('NAME_lc');
544             }
545              
546             sub hashrefs {
547 4     4   5 my $self = shift;
548 4 50       27 return unless $self->{Active};
549              
550 4         5 my @all;
551 4         57 while ( my $ref = $self->fetchrow_hashref('NAME_lc') ) {
552 4         42 push( @all, $ref );
553             }
554              
555 4 100       32 return @all if wantarray;
556 2         22 return \@all;
557             }
558              
559             package DBIx::ThinSQL::bind_value;
560 3     3   10 use strict;
  3         3  
  3         61  
561 3     3   7 use warnings;
  3         3  
  3         446  
562             our @ISA = ('DBIx::ThinSQL::expr');
563              
564             sub new {
565 27     27   30 my $class = shift;
566 27 100       86 return $_[0] if ref( $_[0] ) =~ m/DBIx::ThinSQL/;
567 25 50       44 return $$_[0] if ref $_[0] eq 'SCALAR';
568 25         88 return bless [@_], $class;
569             }
570              
571             sub val {
572 0     0   0 return $_[0]->[0];
573             }
574              
575             sub type {
576 0     0   0 return $_[0]->[1];
577             }
578              
579             sub for_bind_param {
580 25     25   25 my $self = shift;
581              
582             # value, type
583 25 50       50 return @$self if defined $self->[1];
584              
585             # value
586 25         140 return $self->[0];
587             }
588              
589             package DBIx::ThinSQL::quote_value;
590 3     3   12 use strict;
  3         4  
  3         42  
591 3     3   7 use warnings;
  3         2  
  3         415  
592             our @ISA = ('DBIx::ThinSQL::expr');
593              
594             sub new {
595 3     3   5 my $class = shift;
596 3 50       9 return $_[0] if ref( $_[0] ) =~ m/DBIx::ThinSQL/;
597 3 50       8 return $$_[0] if ref $_[0] eq 'SCALAR';
598 3         10 return bless [@_], $class;
599             }
600              
601             sub val {
602 0     0   0 return $_[0]->[0];
603             }
604              
605             sub type {
606 0     0   0 return $_[0]->[1];
607             }
608              
609             sub for_quote {
610 3     3   4 my $self = shift;
611              
612             # value, type
613 3 50       13 return @$self if defined $self->[1];
614              
615             # value
616 3         30 return $self->[0];
617             }
618              
619             package DBIx::ThinSQL::quote_identifier;
620 3     3   9 use strict;
  3         8  
  3         45  
621 3     3   6 use warnings;
  3         3  
  3         178  
622              
623             sub new {
624 1     1   2 my $class = shift;
625 1         1 my $id = shift;
626 1         5 return bless \$id, $class;
627             }
628              
629             sub val {
630 1     1   2 my $self = shift;
631 1         25 return $$self;
632             }
633              
634             package DBIx::ThinSQL::expr;
635 3     3   9 use strict;
  3         3  
  3         41  
636 3     3   7 use warnings;
  3         3  
  3         1212  
637              
638             sub new {
639 19     19   25 my $class = shift;
640 19         16 my @tokens;
641 19         33 foreach my $token (@_) {
642 31 50       66 if ( ref $token eq 'ARRAY' ) {
    100          
643 0         0 push( @tokens, DBIx::ThinSQL::query->new(@$token) );
644             }
645             elsif ( ref $token eq 'HASH' ) {
646 2         7 my @cols = sort keys %$token;
647 2         3 my $narg;
648              
649 2         3 foreach my $col (@cols) {
650 2 50       11 if ( ref $token->{$col} eq 'SCALAR' ) {
    100          
    50          
651 0         0 $narg->{$col} = ${ $token->{$col} };
  0         0  
652             }
653             elsif ( ref $token->{$col} eq 'ARRAY' ) {
654             $narg->{$col} =
655 2         4 [ map { DBIx::ThinSQL::bind_value->new($_) }
656 1         2 @{ $token->{$col} } ];
  1         1  
657             }
658             elsif ( defined $token->{$col} ) {
659             $narg->{$col} =
660 1         4 DBIx::ThinSQL::bind_value->new( $token->{$col} );
661             }
662             }
663              
664 2         4 foreach my $col (@cols) {
665 2         25 my $val = $narg->{$col};
666              
667 2         5 my $like = $col =~ s/\s+like$/ LIKE /i;
668 2         3 my $not_like = $col =~ s/\s+(!|not)\s*like$/ NOT LIKE /i;
669 2         7 my $not = $col =~ s/\s*!$//;
670 2         5 my $gtlt = $col =~ s/(\s+[><]=?)$/$1 /;
671              
672 2         3 push( @tokens, $col );
673 2 50       8 if ( !defined $val ) {
    100          
674 0 0       0 push( @tokens, ' IS ', $not ? 'NOT NULL' : 'NULL' );
675             }
676             elsif ( ref $val eq 'ARRAY' ) {
677 1 50       3 push( @tokens, ' NOT' ) if $not;
678 1         3 push( @tokens, ' IN (', map { $_, ',' } @$val );
  2         4  
679 1 50       4 pop(@tokens) if @$val;
680 1         2 push( @tokens, ')' );
681             }
682             else {
683 1 50 33     12 push( @tokens, $not ? ' != ' : ' = ' )
    50 33        
684             unless $like
685             or $not_like
686             or $gtlt;
687              
688 1         2 push( @tokens, $val );
689             }
690 2         9 push( @tokens, ' AND ' );
691             }
692 2         26 pop @tokens;
693             }
694             else {
695 29         40 push( @tokens, $token );
696             }
697             }
698 19         101 return bless \@tokens, $class;
699             }
700              
701             sub as {
702 0     0   0 my $self = shift;
703 0         0 my $value = shift;
704              
705 0         0 return DBIx::ThinSQL::expr->new( $self, ' AS ',
706             DBIx::ThinSQL::quote_identifier->new($value) );
707             }
708              
709             sub tokens {
710 19     19   17 my $self = shift;
711 19         32 return @$self;
712             }
713              
714             package DBIx::ThinSQL::case;
715 3     3   12 use strict;
  3         1  
  3         51  
716 3     3   7 use warnings;
  3         3  
  3         364  
717             our @ISA = ('DBIx::ThinSQL::expr');
718              
719             sub new {
720 0     0   0 my $class = shift;
721 0         0 my @tokens;
722              
723 0         0 while ( my ( $key, $val ) = splice( @_, 0, 2 ) ) {
724 0         0 ( $key = uc($key) ) =~ s/_/ /g;
725 0         0 push( @tokens, [ $key, DBIx::ThinSQL::expr->new($val) ] );
726             }
727              
728 0         0 return bless \@tokens, $class;
729             }
730              
731             package DBIx::ThinSQL::list;
732 3     3   14 use strict;
  3         1  
  3         43  
733 3     3   7 use warnings;
  3         2  
  3         362  
734              
735             sub new {
736 33     33   25 my $class = shift;
737 33         27 my @tokens;
738 33         41 foreach my $token (@_) {
739 68 50       81 if ( ref $token eq 'ARRAY' ) {
740 0         0 push( @tokens, DBIx::ThinSQL::query->new(@$token) );
741             }
742             else {
743 68         82 push( @tokens, $token );
744             }
745             }
746 33         124 return bless \@tokens, $class;
747             }
748              
749             sub tokens {
750 33     33   28 my $self = shift;
751 33         78 return @$self;
752             }
753              
754             package DBIx::ThinSQL::table;
755             our @ISA = ('DBIx::ThinSQL::list');
756              
757             package DBIx::ThinSQL::values;
758             our @ISA = ('DBIx::ThinSQL::list');
759              
760             package DBIx::ThinSQL::query;
761 3     3   8 use strict;
  3         3  
  3         42  
762 3     3   7 use warnings;
  3         6  
  3         1436  
763              
764             sub new {
765 34     34   33 my $class = shift;
766 34         30 my @query;
767              
768 34         40 eval {
769 34         113 while ( my ( $word, $arg ) = splice( @_, 0, 2 ) ) {
770 89         190 ( $word = uc($word) ) =~ s/_/ /g;
771 89         97 my $ref = ref $arg;
772              
773 89 100       204 if ( $ref =~ m/^DBIx::ThinSQL/ ) {
    100          
    100          
774 1         4 push( @query, [ $word, $arg ] );
775             }
776             elsif ( $ref eq 'ARRAY' ) {
777 29 100       147 if ( $word =~ m/((SELECT)|(ORDER)|(GROUP))/ ) {
    50          
    50          
    100          
778 20         45 push( @query, [ $word, DBIx::ThinSQL::list->new(@$arg) ] );
779             }
780             elsif ( $word =~ m/INSERT/ ) {
781 0         0 push( @query, [ $word, DBIx::ThinSQL::table->new(@$arg) ] );
782             }
783             elsif ( $word =~ m/(^AS$)|(FROM)|(JOIN)/ ) {
784 0         0 push( @query, [ $word, DBIx::ThinSQL::query->new(@$arg) ] );
785             }
786             elsif ( $word eq 'VALUES' ) {
787             push(
788             @query,
789             [
790             $word,
791             DBIx::ThinSQL::values->new(
792 4         9 map { DBIx::ThinSQL::bind_value->new($_) }
  8         17  
793             @$arg
794             )
795             ]
796             );
797             }
798             else {
799 5         15 push( @query, [ $word, DBIx::ThinSQL::expr->new(@$arg) ] );
800             }
801             }
802             elsif ( $ref eq 'HASH' ) {
803              
804 7 100       41 if ( $word =~ m/^((WHERE)|(ON))/ ) {
    100          
    50          
805 2         5 push( @query, [ $word, DBIx::ThinSQL::expr->new($arg) ] );
806             }
807             elsif ( $word eq 'SET' ) {
808 1         5 my @cols = sort keys %$arg;
809             push(
810             @query,
811             [
812             $word,
813             DBIx::ThinSQL::list->new(
814             map {
815 1         4 DBIx::ThinSQL::expr->new(
816              
817             # quote_identifier?
818             $_, ' = ',
819             ref $arg->{$_} eq 'SCALAR'
820 0         0 ? ${ $arg->{$_} }
821             : DBIx::ThinSQL::bind_value->new(
822 1 50       5 $arg->{$_}
823             )
824             )
825             } @cols
826             )
827             ]
828             );
829             }
830             elsif ( $word eq 'VALUES' ) {
831 4         25 my @cols = sort keys %$arg;
832              
833             # map quote_identifier?
834 4         29 $query[-1]->[1] =
835             DBIx::ThinSQL::table->new( $query[-1]->[1], @cols );
836              
837             push(
838             @query,
839             [
840             $word,
841             DBIx::ThinSQL::values->new(
842             map {
843 4         8 ref $arg->{$_} eq 'SCALAR'
844 0         0 ? ${ $arg->{$_} }
845             : DBIx::ThinSQL::bind_value->new(
846 8 50       27 $arg->{$_} )
847             } @cols
848             )
849             ]
850             );
851             }
852             else {
853 0         0 warn "cannot handle $word => HASH";
854             }
855             }
856             else {
857 52         167 push( @query, [ $word, $arg ] );
858             }
859             }
860             };
861              
862 34 50       60 Carp::croak("Bad Query: $@") if $@;
863 34         95 return bless \@query, $class;
864             }
865              
866             sub as {
867 1     1   2 my $self = shift;
868 1         2 my $value = shift;
869              
870 1         10 return DBIx::ThinSQL::expr->new( '(', $self, ') AS ',
871             DBIx::ThinSQL::quote_identifier->new($value) );
872             }
873              
874             sub tokens {
875 34     34   34 my $self = shift;
876 34         82 return @$self;
877             }
878              
879             1;
880              
881             __END__