File Coverage

blib/lib/SQL/Maker/Condition.pm
Criterion Covered Total %
statement 126 128 98.4
branch 52 56 92.8
condition 21 23 91.3
subroutine 19 19 100.0
pod 0 7 0.0
total 218 233 93.5


line stmt bran cond sub pod time code
1             package SQL::Maker::Condition;
2 22     22   114064 use strict;
  22         169  
  22         867  
3 22     22   119 use warnings;
  22         45  
  22         594  
4 22     22   5266 use utf8;
  22         82  
  22         369  
5 22     22   557 use Scalar::Util ();
  22         46  
  22         435  
6 22     22   25862 use SQL::Maker::Util;
  22         65  
  22         2243  
7             use overload
8 5     5   15445 '&' => sub { $_[0]->compose_and($_[1]) },
9 4     4   14227 '|' => sub { $_[0]->compose_or($_[1]) },
10 22     22   50583 fallback => 1;
  22         44591  
  22         352  
11              
12             sub _quote {
13 162     162   359 my ($self, $label) = @_;
14              
15 162 100       365 return $$label if ref $label;
16 159         1106 SQL::Maker::Util::quote_identifier($label, $self->{quote_char}, $self->{name_sep})
17             }
18              
19             sub new {
20 185     185 0 97784 my $class = shift;
21 185 50       1022 my %args = @_==1 ? %{$_[0]} : @_;
  0         0  
22 185         2214 bless {sql => [], bind => [], strict => 0, %args}, $class;
23             }
24              
25             sub _make_term {
26 175     175   313 my ($self, $col, $val) = @_;
27              
28 175 100       656 if (Scalar::Util::blessed($val)) {
29 17 50       114 if ($val->can('as_sql')) {
30 17     17   132 return ($val->as_sql($col, sub { $self->_quote(@_) }), [ $val->bind() ]);
  17         435  
31             } else {
32 0         0 return ($self->_quote($col) . " = ?", [ $val ]);
33             }
34             }
35              
36 158 100 100     1820 Carp::croak("cannot pass in an unblessed ref as an argument in strict mode")
37             if ref($val) && $self->{strict};
38              
39 153 100       880 if ( ref($val) eq 'ARRAY' ) {
    100          
    100          
    100          
40             # make_term(foo => {-and => [1,2,3]}) => (foo = 1) AND (foo = 2) AND (foo = 3)
41 10 100 100     88 if ( ref $val->[0] or ( ( $val->[0] || '' ) eq '-and' ) ) {
      100        
42 3         5 my $logic = 'OR';
43 3         7 my @values = @$val;
44 3 100       13 if ( $val->[0] eq '-and' ) {
45 2         4 $logic = 'AND';
46 2         4 shift @values;
47             }
48              
49 3         8 my @bind;
50             my @terms;
51 3         7 for my $v (@values) {
52 7         30 my ( $term, $bind ) = $self->_make_term( $col, $v );
53 7         20 push @terms, "($term)";
54 7         18 push @bind, @$bind;
55             }
56 3         11 my $term = join " $logic ", @terms;
57 3         14 return ($term, \@bind);
58             }
59             else {
60             # make_term(foo => [1,2,3]) => foo IN (1,2,3)
61 7         27 return $self->_make_term_by_arrayref($col, 'IN', $val);
62             }
63             }
64             elsif ( ref($val) eq 'HASH' ) {
65 19         32 my ( $op, $v ) = ( %{$val} );
  19         71  
66 19         53 $op = uc($op);
67 19 100 100     272 if ( ( $op eq 'IN' || $op eq 'NOT IN' ) && ref($v) eq 'ARRAY' ) {
    100 100        
    100 100        
      66        
      66        
68 8         26 return $self->_make_term_by_arrayref($col, $op, $v);
69             }
70             elsif ( ( $op eq 'IN' || $op eq 'NOT IN' ) && ref($v) eq 'REF' ) {
71             # make_term(foo => +{ 'IN', \['SELECT foo FROM bar'] }) => foo IN (SELECT foo FROM bar)
72 3         5 my @values = @{$$v};
  3         10  
73 3         12 my $term = $self->_quote($col) . " $op (" . shift(@values) . ')';
74 3         16 return ($term, \@values);
75             }
76             elsif ( ( $op eq 'BETWEEN' ) && ref($v) eq 'ARRAY' ) {
77 1 50       5 Carp::croak("USAGE: make_term(foo => {BETWEEN => [\$a, \$b]})") if @$v != 2;
78 1         5 return ($self->_quote($col) . " BETWEEN ? AND ?", $v);
79             }
80             else {
81 7 100       19 if (ref($v) eq 'SCALAR') {
82             # make_term(foo => +{ '<', \"DATE_SUB(NOW(), INTERVAL 3 DAY)"}) => 'foo < DATE_SUB(NOW(), INTERVAL 3 DAY)'
83 1         5 return ($self->_quote($col) . " $op " . $$v, []);
84             }
85             else {
86             # make_term(foo => +{ '<', 3 }) => foo < 3
87 6         21 return ($self->_quote($col) . " $op ?", [$v]);
88             }
89             }
90             }
91             elsif ( ref($val) eq 'SCALAR' ) {
92             # make_term(foo => \"> 3") => foo > 3
93 1         4 return ($self->_quote($col) . " $$val", []);
94             }
95             elsif ( ref($val) eq 'REF') {
96 2         3 my ($query, @v) = @{${$val}};
  2         4  
  2         7  
97 2         7 return ($self->_quote($col) . " $query", \@v);
98             }
99             else {
100 121 100       252 if (defined $val) {
101             # make_term(foo => "3") => foo = 3
102 119         313 return ($self->_quote($col) . " = ?", [$val]);
103             } else {
104             # make_term(foo => undef) => foo IS NULL
105 2         11 return ($self->_quote($col) . " IS NULL", []);
106             }
107             }
108             }
109              
110             sub _make_term_by_arrayref {
111 15     15   34 my ($self, $col, $op, $v) = @_;
112 15 100       35 if (@$v == 0) {
113 5 100       18 if ($op eq 'IN') {
114             # make_term(foo => +{'IN' => []}) => 0=1
115 3         14 return ('0=1', []);
116             } else {
117             # make_term(foo => +{'NOT IN' => []}) => 1=1
118 2         12 return ('1=1', []);
119             }
120             } else {
121             # make_term(foo => +{ 'IN', [1,2,3] }) => foo IN (1,2,3)
122 10         32 my $term = $self->_quote($col) . " $op (" . join( ', ', ('?') x scalar @$v ) . ')';
123 10         42 return ($term, $v);
124             }
125             }
126              
127             sub add {
128 168     168 0 738 my ( $self, $col, $val ) = @_;
129              
130 168         501 my ( $term, $bind ) = $self->_make_term( $col, $val );
131 163         585 push @{ $self->{sql} }, "($term)";
  163         593  
132 163         233 push @{ $self->{bind} }, @$bind;
  163         459  
133              
134 163         628 return $self; # for influent interface
135             }
136              
137             sub add_raw {
138 12     12 0 42 my ($self, $term, $bind) = @_;
139              
140 12         14 push @{ $self->{sql} }, "($term)";
  12         73  
141 12 100       34 if ( defined $bind ) {
142 8 100       11 push @{ $self->{bind} }, (ref($bind) eq 'ARRAY' ? @$bind : $bind);
  8         32  
143             }
144              
145 12         26 return $self;
146             }
147              
148             sub compose_and {
149 5     5 0 12 my ($self, $other) = @_;
150              
151 5 100       11 if ( !@{$self->{sql}} ) {
  5         25  
152 2 100       3 if ( !@{$other->{sql}} ) {
  2         11  
153 1         9 return SQL::Maker::Condition->new;
154             }
155 1         6 return SQL::Maker::Condition->new(
156             sql => ['(' . $other->as_sql() . ')'],
157 1         6 bind => [@{$other->{bind}}],
158             );
159             }
160 3 100       8 if ( !@{$other->{sql}} ) {
  3         14  
161 1         6 return SQL::Maker::Condition->new(
162             sql => ['(' . $self->as_sql() . ')'],
163 1         6 bind => [@{$self->{bind}}],
164             );
165             }
166              
167 2         5 return SQL::Maker::Condition->new(
168             sql => ['(' . $self->as_sql() . ') AND (' . $other->as_sql() . ')'],
169 2         8 bind => [@{$self->{bind}}, @{$other->{bind}}],
  2         10  
170             );
171             }
172              
173             sub compose_or {
174 4     4 0 11 my ($self, $other) = @_;
175              
176 4 100       10 if ( !@{$self->{sql}} ) {
  4         24  
177 2 100       4 if ( !@{$other->{sql}} ) {
  2         578  
178 1         8 return SQL::Maker::Condition->new;
179             }
180 1         8 return SQL::Maker::Condition->new(
181             sql => ['(' . $other->as_sql() . ')'],
182 1         139 bind => [@{$other->{bind}}],
183             );
184             }
185 2 100       7 if ( !@{$other->{sql}} ) {
  2         11  
186 1         7 return SQL::Maker::Condition->new(
187             sql => ['(' . $self->as_sql() . ')'],
188 1         555 bind => [@{$self->{bind}}],
189             );
190             }
191              
192             # return value is enclosed with '()'.
193             # because 'OR' operator priority less than 'AND'.
194 1         4 return SQL::Maker::Condition->new(
195             sql => ['((' . $self->as_sql() . ') OR (' . $other->as_sql() . '))'],
196 1         6 bind => [@{$self->{bind}}, @{$other->{bind}}],
  1         7  
197             );
198             }
199              
200             sub as_sql {
201 247     247 0 680 my ($self) = @_;
202 247         537 return join(' AND ', @{$self->{sql}});
  247         2014  
203             }
204              
205             sub bind {
206 238     238 0 22520 my $self = shift;
207 238 50       534 return wantarray ? @{$self->{bind}} : $self->{bind};
  238         1241  
208             }
209              
210             1;
211             __END__