File Coverage

blib/lib/SQL/DB/Expr.pm
Criterion Covered Total %
statement 248 294 84.3
branch 72 110 65.4
condition 9 19 47.3
subroutine 59 65 90.7
pod 13 13 100.0
total 401 501 80.0


line stmt bran cond sub pod time code
1             package SQL::DB::Expr;
2 6     6   116554 use strict;
  6         11  
  6         212  
3 6     6   32 use warnings;
  6         10  
  6         185  
4 6     6   12708 use DBI qw/looks_like_number :sql_types/;
  6         48312  
  6         3176  
5 6     6   6689 use Moo;
  6         98359  
  6         42  
6 6     6   18541 use Carp qw/ carp croak confess/;
  6         10  
  6         554  
7 6         74 use Sub::Exporter -setup => {
8             exports => [
9             qw/
10             AND
11             OR
12             _sql
13             _quote
14             _bval
15             _expr_binary
16             _expr_join
17             _query
18             /
19             ],
20             groups => { default => [qw/ /], },
21 6     6   18259 };
  6         101981  
22             use overload
23 6         61 '""' => '_as_string',
24             '!' => '_expr_not',
25             '==' => '_expr_eq',
26             'eq' => '_expr_eq',
27             '!=' => '_expr_ne',
28             'ne' => '_expr_ne',
29             '&' => '_expr_bitand',
30             '|' => '_expr_bitor',
31             '<' => '_expr_lt',
32             '>' => '_expr_gt',
33             '<=' => '_expr_lte',
34             '>=' => '_expr_gte',
35             '+' => '_expr_add',
36             '-' => '_expr_sub',
37             '*' => '_expr_mult',
38             '/' => '_expr_divide',
39             '.' => '_expr_addstr',
40             '.=' => '_expr_addstr',
41             fallback => 1,
42 6     6   3602 ;
  6         14  
43              
44             our $VERSION = '0.971.0';
45             our $tcount = {};
46              
47             # ########################################################################
48             # FUNCTIONS
49             # ########################################################################
50              
51             sub AND {
52 15     15 1 328 SQL::DB::Expr->new(
53             _txt => [' AND '],
54             _logic => 1
55             );
56             }
57              
58             sub OR {
59 11     11 1 214 SQL::DB::Expr->new(
60             _txt => [' OR '],
61             _logic => 1
62             );
63             }
64              
65             sub _sql {
66 3     3   242 my $val = shift;
67              
68 3 50       14 return $val if ( ref $val ) =~ m/^SQL::DB::Expr/;
69              
70 3         81 return SQL::DB::Expr::SQL->new( val => $val );
71             }
72              
73             sub _quote {
74 16     16   337 my $val = shift;
75              
76 16 100       98 return $val if ( ref $val ) =~ m/^SQL::DB::Expr/;
77              
78 9         289 return SQL::DB::Expr::Quote->new( val => $val );
79             }
80              
81             sub _bval {
82 117     117   776 my ( $val, $type ) = @_;
83              
84 117 100       523 return $val if ( ref $val ) =~ m/^SQL::DB::Expr/;
85              
86 64         1342 return SQL::DB::Expr::BindValue->new( val => $val, type => $type );
87             }
88              
89             sub _expr_join {
90 28     28   261 my $sep = shift;
91 28         58 my $last = pop @_;
92              
93             my $e = SQL::DB::Expr->new(
94             _txt => [
95             (
96             map {
97 34 100       77 eval { $_->isa('SQL::DB::Expr') }
  34         292  
98             ? ( $_->_txts, $sep )
99             : ( $_, $sep )
100             } @_
101             ),
102 28 100       61 eval { $last->isa('SQL::DB::Expr') } ? $last->_txts : $last
  28         625  
103             ]
104             );
105 28         210 return $e;
106             }
107              
108             sub _query {
109 17 50 33 17   85 return $_[0] if ( @_ == 1 and eval { $_[0]->isa('SQL::DB::Expr') } );
  0         0  
110 17         514 my $e = SQL::DB::Expr->new;
111              
112 17         81 eval {
113 17         124 while ( my ( $keyword, $item ) = splice( @_, 0, 2 ) )
114             {
115 42 100       114 if ( ref $keyword ) {
116 5         16 $e .= $keyword . "\n";
117             }
118             else {
119 37         139 ( my $tmp = uc($keyword) ) =~ s/_/ /g;
120 37         153 $e .= $tmp . "\n";
121             }
122              
123 42 100       122 next unless defined $item;
124 37 100       212 if ( ref $item eq 'SQL::DB::Expr' ) {
    100          
    100          
125 10         33 $e .= ' ' . $item . "\n";
126             }
127             elsif ( ref $item eq 'ARRAY' ) {
128 11 50       27 my @new = map { ref $_ ? $_ : _bval($_) } @$item;
  25         90  
129 11         48 $e .= ' ' . _expr_join( ",\n ", @new ) . "\n";
130             }
131             elsif ( ref $item eq 'SCALAR' ) {
132 1         5 $e .= ' ' . $$item . "\n";
133             }
134             else {
135 15         340 $e .= ' ' . $item . "\n";
136             }
137              
138 37         119 $e->_multi(0);
139             }
140             };
141              
142 17 50       48 confess "Bad Query: $@" if $@;
143 17         54 return $e;
144             }
145              
146             # ########################################################################
147             # OBJECT INTERFACE
148             # ########################################################################
149              
150             has '_txt' => (
151             is => 'rw',
152             isa =>
153             sub { confess "Must be ARRAY ref: $_[0]" unless ref $_[0] eq 'ARRAY' },
154             default => sub { [] },
155             );
156              
157             has '_alias' => ( is => 'rw', );
158              
159             has '_type' => ( is => 'rw', );
160              
161             has '_multi' => (
162             is => 'rw',
163             default => sub { 0 },
164             );
165              
166             has '_logic' => (
167             is => 'rw',
168             default => sub { 0 },
169             );
170              
171             sub BUILD {
172 409     409 1 4727 my $self = shift;
173              
174 409 100       9192 if ( my $name = $self->_alias ) {
175 6   100     52 $tcount->{$name} ||= [];
176 6         12 my $i = 0;
177 6         25 while ( $tcount->{$name}->[$i] ) {
178 2         7 $i++;
179             }
180 6         17 $tcount->{$name}->[$i] = 1;
181 6         31 $self->_alias( $name . $i );
182 6         128 $self->_txt( [ $name . ' AS ' . $name . $i ] );
183             }
184             }
185              
186             sub _txts {
187 558     558   1833 return @{ shift->_txt };
  558         13057  
188             }
189              
190             sub _clone {
191 0     0   0 my $self = shift;
192 0         0 bless {%$self}, ref $self;
193             }
194              
195             sub _as_string {
196 100     100   37686 my $self = shift;
197              
198 100 50       195 return join( '', map { defined $_ ? $_ : '*UNDEF*' } $self->_txts );
  467         1782  
199             }
200              
201             sub _as_pretty {
202 0     0   0 my $self = shift;
203 0         0 my $dbh = shift;
204              
205 0         0 my $sql;
206              
207 0         0 foreach my $token ( $self->_txts ) {
208 0 0       0 if ( ref $token eq 'SQL::DB::Expr::Quote' ) {
    0          
209 0         0 $sql .= $dbh->quote( $token->val );
210             }
211             elsif ( ref $token eq 'SQL::DB::Expr::BindValue' ) {
212 0         0 my $val = $token->val;
213 0         0 my $type = $token->type;
214              
215 0 0       0 if ( !defined $val ) {
    0          
    0          
216 0         0 $sql .= $dbh->quote(undef);
217 0         0 next;
218             }
219 5     5   11867 elsif ( $val =~ /[\P{IsPrint}]/ ) {
  5         48  
  5         57  
220 0         0 $sql .= '/*BINARY DATA*/';
221             }
222             elsif ( looks_like_number($val) ) {
223 0         0 $sql .= $val;
224             }
225             else {
226 0         0 ( my $x = $val ) =~ s/\n.*/\.\.\./s;
227 0         0 $sql .= $dbh->quote($val);
228             }
229             }
230             else {
231 0         0 $sql .= $token;
232             }
233             }
234 0         0 return $sql . ';';
235              
236             }
237              
238             my %type_map = (
239             biginteger => { TYPE => SQL_BIGINT },
240             bigint => { TYPE => SQL_BIGINT },
241             binary => { TYPE => SQL_BINARY },
242             'binary varying' => { TYPE => SQL_VARBINARY },
243             bin => { TYPE => SQL_BINARY },
244             bit => { TYPE => SQL_BIT },
245             blob => { TYPE => SQL_BLOB },
246             character => { TYPE => SQL_CHAR },
247             'character varying' => { TYPE => SQL_VARCHAR },
248             char => { TYPE => SQL_CHAR },
249             clob => { TYPE => SQL_CLOB },
250             datetime => { TYPE => SQL_DATETIME },
251             date => { TYPE => SQL_DATE },
252             decimal => { TYPE => SQL_DECIMAL },
253             double => { TYPE => SQL_DOUBLE },
254             float => { TYPE => SQL_FLOAT },
255             integer => { TYPE => SQL_INTEGER },
256             interval => { TYPE => SQL_INTERVAL },
257             int => { TYPE => SQL_INTEGER },
258             numeric => { TYPE => SQL_NUMERIC },
259             real => { TYPE => SQL_REAL },
260             smallinteger => { TYPE => SQL_SMALLINT },
261             smallint => { TYPE => SQL_SMALLINT },
262             text => { TYPE => SQL_VARCHAR },
263             timestamp => { TYPE => SQL_TIMESTAMP },
264             varbin => { TYPE => SQL_VARBINARY },
265             varchar => { TYPE => SQL_VARCHAR },
266             );
267              
268             sub _sql_values_types {
269 17     17   41 my $self = shift;
270 17         24 my $dbh = shift;
271              
272 17         27 my $sql;
273             my @values;
274 0         0 my @types;
275              
276 17         52 foreach my $token ( $self->_txts ) {
277              
278 228 100       806 if ( ref $token eq 'SQL::DB::Expr::Quote' ) {
    100          
    50          
279 5         88 $sql .= $dbh->quote( $token->val );
280             }
281             elsif ( ref $token eq 'SQL::DB::Expr::BindValue' ) {
282 13         55 my $val = $token->val;
283 13         55 my $type = $token->type;
284              
285 13 50       93 if ( !defined $val ) {
    100          
286 0         0 $sql .= $dbh->quote(undef);
287 0         0 next;
288             }
289             elsif ( defined $type ) {
290 12         31 push( @values, $val );
291 12 50       45 if ( $type_map{$type} ) {
    0          
    0          
    0          
    0          
292 12         42 push( @types, $type_map{$type} );
293             }
294             elsif ( $type eq 'bytea' ) {
295 0         0 push( @types, { pg_type => eval 'DBD::Pg::PG_BYTEA' } );
296             }
297             elsif ( $type eq 'inet' ) {
298 0         0 push( @types, { pg_type => eval 'DBD::Pg::PG_INET' } );
299             }
300             elsif ( $type eq 'cidr' ) {
301 0         0 push( @types, { pg_type => eval 'DBD::Pg::PG_CIDR' } );
302             }
303             elsif ( $type eq 'boolean' ) {
304 0         0 push( @types, { pg_type => eval 'DBD::Pg::PG_BOOL' } );
305             }
306             else {
307 0         0 warn "No mapping for type $type";
308 0         0 push( @types, undef );
309             }
310              
311 12         26 $sql .= '?';
312              
313             # leave it undefined
314             }
315             else {
316 1         10 $sql .= $dbh->quote($val);
317             }
318             }
319             elsif ( !defined $token ) {
320 0         0 warn 'undefined token received! SQL so far:' . $sql;
321             }
322             else {
323 210         343 $sql .= $token;
324             }
325             }
326              
327 17         103 return ( $sql, \@values, \@types );
328             }
329              
330             # A true internal function - don't use outside this package
331             sub _push {
332 339     339   1474 my $self = shift;
333 339         349 push( @{ $self->_txt }, @_ );
  339         15658  
334             }
335              
336             # A true internal function - don't use outside this package
337             sub _unshift {
338 59     59   67 my $self = shift;
339 59         57 unshift( @{ $self->_txt }, @_ );
  59         1182  
340             }
341              
342             sub _expr_addstr {
343 475     475   4571 my ( $e1, $e2, $swap ) = @_;
344              
345             # The argument is undef
346 475 50       1142 if ( !defined $e2 ) {
347 0         0 Carp::carp('Use of uninitialized value in concatenation (. or .=)');
348 0         0 return $e1;
349             }
350              
351 475         535 my $res;
352              
353 475   100     969 my $multi = $e1->_multi + ( eval { $e2->_multi } || 0 );
354              
355             # $e2 . $e1 (or $e2 .= $e1)
356 475 100       1340 if ($swap) {
    100          
357 40 50       165 if ( eval { $e2->isa(__PACKAGE__) } ) {
  40         287  
358 0         0 $res = __PACKAGE__->new(
359             _txt => [ $e2->_txts, $e1->_txts ],
360             _multi => $multi,
361             _logic => $e1->_logic,
362             );
363             }
364             else {
365 40         120 $res = __PACKAGE__->new(
366             _txt => [ $e2, $e1->_txts ],
367             _multi => $multi,
368             _logic => $e1->_logic,
369             );
370             }
371             }
372              
373             # $e1 . $e2
374             elsif ( defined $swap ) {
375              
376 135         146 my $logic = 0;
377 135         185 my $multi = 0;
378 135 100       176 if ( eval { $e2->_logic } ) {
  135 50       1080  
379 24 100       58 if ( $e1->_multi ) {
380 10         27 $e1->_unshift('(');
381 10         81 $e1->_push(')');
382             }
383 24         83 $logic = 1;
384             }
385             elsif ( $e1->_logic ) {
386 0 0       0 if ( eval { $e2->_multi } ) {
  0         0  
387 0         0 $e2->_unshift('(');
388 0         0 $e2->_push(')');
389             }
390 0         0 $multi = 0;
391             }
392              
393 135 100       405 if ( eval { $e2->isa(__PACKAGE__) } ) {
  135         700  
394 46         97 $res = __PACKAGE__->new(
395             _txt => [ $e1->_txts, $e2->_txts ],
396             _multi => $multi,
397             _logic => $logic,
398             );
399             }
400             else {
401 89         228 $res = __PACKAGE__->new(
402             _txt => [ $e1->_txts, $e2 ],
403             _multi => $multi,
404             _logic => $logic,
405             );
406             }
407             }
408              
409             # $e1 .= $e2
410             else {
411 300         388 my $logic = 0;
412 300         320 my $multi = 0;
413 300 100       331 if ( eval { $e2->_logic } ) {
  300 100       2014  
414 2 50       8 if ( $e1->_multi ) {
415 2         5 $e1->_unshift('(');
416 2         12 $e1->_push(')');
417             }
418 2         10 $logic = 1;
419             }
420             elsif ( $e1->_logic ) {
421 26 100       25 if ( eval { $e2->_multi } ) {
  26         98  
422 11         25 $e2->_unshift('(');
423 11         78 $e2->_push(')');
424             }
425 26         92 $multi = 1;
426             }
427              
428 300 100       528 if ( eval { $e2->isa(__PACKAGE__) } ) {
  300         2854  
429 179         329 $e1->_push( $e2->_txts );
430 179         1485 $e1->_multi($multi);
431 179         272 $e1->_logic($logic);
432             }
433             else {
434 121         286 $e1->_push($e2);
435 121         1093 $e1->_multi($multi);
436 121         203 $e1->_logic($logic);
437             }
438 300         490 $res = $e1;
439             }
440              
441 475         1969 return $res;
442             }
443              
444             sub _expr_not {
445 20     20   83 my $e1 = shift;
446 20         658 my $expr = SQL::DB::Expr->new . $e1;
447              
448 20 100       45 if ( $e1->_multi > 0 ) {
449 16         33 $expr->_unshift('(');
450 16         114 $expr->_push(')');
451             }
452 20         120 $expr->_unshift('NOT ');
453 20         132 $expr->_multi(0);
454 20         70 return $expr;
455             }
456              
457             sub _expr_binary {
458 64     64   138 my ( $op, $e1, $e2, $swap, $_multi ) = @_;
459              
460 64         1423 my $e = SQL::DB::Expr->new;
461              
462             # TODO add ( ) bracketing for multi expressions?
463 64 100       280 if ($swap) {
464 6         15 $e .= _bval( $e2, $e1->_type );
465 6         19 $e .= ( ' ' . $op . ' ' ) . $e1;
466             }
467             else {
468 58         201 $e .= $e1 . ( ' ' . $op . ' ' );
469 58         309 $e .= _bval( $e2, $e1->_type );
470             }
471              
472 64         116 $e->_multi(1);
473 64         438 return $e;
474             }
475              
476 37     37   159 sub _expr_eq { _expr_binary( '=', @_ ) }
477              
478 6     6   80 sub _expr_ne { _expr_binary( '!=', @_ ) }
479              
480 0     0   0 sub _expr_bitand { _expr_binary( '&', @_ ) }
481              
482 0     0   0 sub _expr_bitor { _expr_binary( '|', @_ ) }
483              
484 4     4   12 sub _expr_lt { _expr_binary( '<', @_ ) }
485              
486 3     3   8 sub _expr_gt { _expr_binary( '>', @_ ) }
487              
488 3     3   8 sub _expr_lte { _expr_binary( '<=', @_ ) }
489              
490 3     3   9 sub _expr_gte { _expr_binary( '>=', @_ ) }
491              
492 2     2   7 sub _expr_add { _expr_binary( '+', @_ ) }
493              
494 3     3   7 sub _expr_sub { _expr_binary( '-', @_ ) }
495              
496 1     1   4 sub _expr_mult { _expr_binary( '*', @_ ) }
497              
498 2     2   6 sub _expr_divide { _expr_binary( '/', @_ ) }
499              
500 2     2 1 5 sub is_null { $_[0] . ' IS NULL' }
501              
502 1     1 1 3 sub is_not_null { $_[0] . ' IS NOT NULL' }
503              
504             sub in {
505 5     5 1 6 my $e1 = shift;
506 5 50 33     21 if ( @_ >= 2 && $_[0] =~ m/^select/i ) {
507 0         0 return $e1 . " IN (\n" . _query(@_) . ')';
508             }
509             return
510 10         92 $e1 . ' IN ('
511 5         8 . _expr_join( ', ', map { _bval( $_, $e1->_type ) } @_ ) . ')';
512             }
513              
514             sub not_in {
515 4     4 1 6 my $e1 = shift;
516 4 50 33     18 if ( @_ >= 2 && $_[0] =~ m/^select/i ) {
517 0         0 return $e1 . " NOT IN (\n" . _query(@_) . ')';
518             }
519             return
520 8         88 $e1
521             . ' NOT IN ('
522 4         7 . _expr_join( ', ', map { _bval( $_, $e1->_type ) } @_ ) . ')';
523             }
524              
525             sub between {
526 6     6 1 12 my $e1 = shift;
527 6 50       14 croak 'between($a,$b)' unless @_ == 2;
528              
529 6         12 my $e = SQL::DB::Expr->new(
530             _txt => [
531             $e1->_txts,
532             ' BETWEEN ',
533             _bval( $_[0], $e1->_type ),
534             ' AND ',
535             _bval( $_[1], $e1->_type )
536             ],
537             );
538 6         41 return $e;
539             }
540              
541             sub not_between {
542 4     4 1 6 my $e1 = shift;
543 4 50       14 croak 'not_between($a,$b)' unless @_ == 2;
544              
545 4         10 my $e = SQL::DB::Expr->new(
546             _txt => [
547             $e1->_txts,
548             ' NOT BETWEEN ',
549             _bval( $_[0], $e1->_type ),
550             ' AND ',
551             _bval( $_[1], $e1->_type )
552             ],
553             );
554 4         27 return $e;
555             }
556              
557             sub as {
558 2     2 1 5 my $e1 = shift;
559 2   33     6 my $as = shift || croak 'as($value)';
560              
561 2 100       10 if ( $e1->_multi > 0 ) {
562 1         24 my $expr = SQL::DB::Expr->new( _txt => ['('] );
563 1         5 $expr .= $e1;
564 1         4 $expr .= ') AS "' . $as . '"';
565 1         6 return $expr;
566             }
567              
568 1         2 return $e1 . ' AS "' . $as . '"';
569             }
570              
571             sub like {
572 5     5 1 44 my $e1 = shift;
573 5   33     14 my $like = shift || croak 'like($value)';
574 5         9 my $expr = $e1 . ' LIKE ';
575 5         14 $expr .= _bval( $like, $e1->_type );
576 5         10 $expr->_multi(0);
577 5         22 return $expr;
578             }
579              
580             sub asc {
581 0     0 1 0 my $e1 = shift;
582 0         0 return $e1 . ' ASC';
583             }
584              
585             sub desc {
586 0     0 1 0 my $e1 = shift;
587 0         0 return $e1 . ' DESC';
588             }
589              
590             DESTROY {
591 408     408   6430 my $self = shift;
592 408 100       3097 if ( my $alias = $self->_alias ) {
593 6         39 $alias =~ m/^(.*?)(\d+)$/;
594 6         201 delete $tcount->{$1}->[$2];
595             }
596             }
597              
598             package SQL::DB::Expr::SQL;
599 6     6   174049 use strict;
  6         17  
  6         264  
600 6     6   35 use warnings;
  6         16  
  6         254  
601 6     6   32 use Moo;
  6         13  
  6         66  
602             use overload '""' => sub {
603 13     13   3026 my $self = shift;
604 13         88 $self->val;
605             },
606 6     6   2218 fallback => 1;
  6         13  
  6         79  
607              
608             has val => (
609             is => 'ro',
610             required => 1,
611             );
612              
613             package SQL::DB::Expr::Quote;
614 6     6   554 use strict;
  6         9  
  6         162  
615 6     6   86 use warnings;
  6         13  
  6         144  
616 6     6   28 use Moo;
  6         11  
  6         24  
617             use overload '""' => sub {
618 14     14   5306 my $self = shift;
619 14 50       135 return 'q{' . ( defined $self->val ? $self->val : 'undef' ) . '}';
620             },
621 6     6   1864 fallback => 1;
  6         60  
  6         45  
622              
623             has val => (
624             is => 'ro',
625             required => 1,
626             );
627              
628             package SQL::DB::Expr::BindValue;
629 6     6   888 use strict;
  6         33  
  6         153  
630 6     6   27 use warnings;
  6         25  
  6         135  
631 6     6   26 use Moo;
  6         11  
  6         23  
632 6     6   1903 use Carp qw/confess/;
  6         12  
  6         767  
633             use overload '""' => sub {
634 63     63   9312 my $self = shift;
635             return
636 63 50       648 'bv{'
    100          
637             . ( defined $self->val ? $self->val : 'undef' ) . '}::'
638             . ( defined $self->type ? $self->type : '(none)' );
639             },
640 6     6   31 fallback => 1;
  6         11  
  6         42  
641              
642             has val => (
643             is => 'ro',
644             required => 1,
645             );
646              
647             has type => ( is => 'rw', );
648              
649             1;