File Coverage

blib/lib/SQL/Maker/Condition.pm
Criterion Covered Total %
statement 100 128 78.1
branch 37 56 66.0
condition 11 23 47.8
subroutine 18 19 94.7
pod 0 7 0.0
total 166 233 71.2


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