File Coverage

blib/lib/SQL/Maker/Condition.pm
Criterion Covered Total %
statement 127 128 99.2
branch 53 56 94.6
condition 21 23 91.3
subroutine 19 19 100.0
pod 0 7 0.0
total 220 233 94.4


line stmt bran cond sub pod time code
1             use strict;
2 24     24   407841 use warnings;
  24         104  
  24         673  
3 24     24   127 use utf8;
  24         45  
  24         536  
4 24     24   2397 use Scalar::Util ();
  24         103  
  24         113  
5 24     24   527 use SQL::Maker::Util;
  24         50  
  24         562  
6 24     24   9025 use overload
  24         66  
  24         2039  
7             '&' => sub { $_[0]->compose_and($_[1]) },
8 5     5   10253 '|' => sub { $_[0]->compose_or($_[1]) },
9 4     4   14079 fallback => 1;
10 24     24   28654  
  24         23923  
  24         278  
11             my ($self, $label) = @_;
12              
13 198     198   384 return $$label if ref $label;
14             SQL::Maker::Util::quote_identifier($label, $self->{quote_char}, $self->{name_sep})
15 198 100       420 }
16              
17 195         810 my $class = shift;
18             my %args = @_==1 ? %{$_[0]} : @_;
19             bless {sql => [], bind => [], strict => 0, %args}, $class;
20 226     226 0 131248 }
21 226 50       959  
  0         0  
22 226         1679 my ($self, $col, $val) = @_;
23              
24             if (Scalar::Util::blessed($val)) {
25             if ($val->can('as_sql')) {
26 211     211   459 return ($val->as_sql($col, sub { $self->_quote(@_) }), [ $val->bind() ]);
27             } else {
28 211 100       637 return ($self->_quote($col) . " = ?", [ $val ]);
29 25 100       115 }
30 23     23   111 }
  23         552  
31              
32 2         6 Carp::croak("cannot pass in an unblessed ref as an argument in strict mode")
33             if ref($val) && $self->{strict};
34              
35             if ( ref($val) eq 'ARRAY' ) {
36             # make_term(foo => {-and => [1,2,3]}) => (foo = 1) AND (foo = 2) AND (foo = 3)
37 186 100 100     1105 if ( ref $val->[0] or ( ( $val->[0] || '' ) eq '-and' ) ) {
38             my $logic = 'OR';
39 181 100       732 my @values = @$val;
    100          
    100          
    100          
40             if ( $val->[0] eq '-and' ) {
41 10 100 100     75 $logic = 'AND';
      100        
42 3         5 shift @values;
43 3         9 }
44 3 100       11  
45 2         3 my @bind;
46 2         4 my @terms;
47             for my $v (@values) {
48             my ( $term, $bind ) = $self->_make_term( $col, $v );
49 3         5 push @terms, "($term)";
50             push @bind, @$bind;
51 3         7 }
52 7         25 my $term = join " $logic ", @terms;
53 7         28 return ($term, \@bind);
54 7         19 }
55             else {
56 3         8 # make_term(foo => [1,2,3]) => foo IN (1,2,3)
57 3         12 return $self->_make_term_by_arrayref($col, 'IN', $val);
58             }
59             }
60             elsif ( ref($val) eq 'HASH' ) {
61 7         27 my ( $op, $v ) = ( %{$val} );
62             $op = uc($op);
63             if ( ( $op eq 'IN' || $op eq 'NOT IN' ) && ref($v) eq 'ARRAY' ) {
64             return $self->_make_term_by_arrayref($col, $op, $v);
65 19         32 }
  19         63  
66 19         53 elsif ( ( $op eq 'IN' || $op eq 'NOT IN' ) && ref($v) eq 'REF' ) {
67 19 100 100     163 # make_term(foo => +{ 'IN', \['SELECT foo FROM bar'] }) => foo IN (SELECT foo FROM bar)
    100 100        
    100 100        
      66        
      66        
68 8         28 my @values = @{$$v};
69             my $term = $self->_quote($col) . " $op (" . shift(@values) . ')';
70             return ($term, \@values);
71             }
72 3         8 elsif ( ( $op eq 'BETWEEN' ) && ref($v) eq 'ARRAY' ) {
  3         11  
73 3         10 Carp::croak("USAGE: make_term(foo => {BETWEEN => [\$a, \$b]})") if @$v != 2;
74 3         15 return ($self->_quote($col) . " BETWEEN ? AND ?", $v);
75             }
76             else {
77 1 50       4 if (ref($v) eq 'SCALAR') {
78 1         4 # make_term(foo => +{ '<', \"DATE_SUB(NOW(), INTERVAL 3 DAY)"}) => 'foo < DATE_SUB(NOW(), INTERVAL 3 DAY)'
79             return ($self->_quote($col) . " $op " . $$v, []);
80             }
81 7 100       19 else {
82             # make_term(foo => +{ '<', 3 }) => foo < 3
83 1         4 return ($self->_quote($col) . " $op ?", [$v]);
84             }
85             }
86             }
87 6         14 elsif ( ref($val) eq 'SCALAR' ) {
88             # make_term(foo => \"> 3") => foo > 3
89             return ($self->_quote($col) . " $$val", []);
90             }
91             elsif ( ref($val) eq 'REF') {
92             my ($query, @v) = @{${$val}};
93 1         4 return ($self->_quote($col) . " $query", \@v);
94             }
95             else {
96 2         4 if (defined $val) {
  2         4  
  2         8  
97 2         7 # make_term(foo => "3") => foo = 3
98             return ($self->_quote($col) . " = ?", [$val]);
99             } else {
100 149 100       296 # make_term(foo => undef) => foo IS NULL
101             return ($self->_quote($col) . " IS NULL", []);
102 147         353 }
103             }
104             }
105 2         6  
106             my ($self, $col, $op, $v) = @_;
107             if (@$v == 0) {
108             if ($op eq 'IN') {
109             # make_term(foo => +{'IN' => []}) => 0=1
110             return ('0=1', []);
111 15     15   45 } else {
112 15 100       51 # make_term(foo => +{'NOT IN' => []}) => 1=1
113 5 100       16 return ('1=1', []);
114             }
115 3         13 } else {
116             # make_term(foo => +{ 'IN', [1,2,3] }) => foo IN (1,2,3)
117             my $term = $self->_quote($col) . " $op (" . '?, ' x (scalar @$v - 1) . '?)';
118 2         10 return ($term, $v);
119             }
120             }
121              
122 10         47 my ( $self, $col, $val ) = @_;
123 10         42  
124             my ( $term, $bind ) = $self->_make_term( $col, $val );
125             push @{ $self->{sql} }, "($term)";
126             push @{ $self->{bind} }, @$bind;
127              
128 204     204 0 795 return $self; # for influent interface
129             }
130 204         509  
131 199         746 my ($self, $term, $bind) = @_;
  199         641  
132 199         326  
  199         464  
133             push @{ $self->{sql} }, "($term)";
134 199         631 if ( defined $bind ) {
135             push @{ $self->{bind} }, (ref($bind) eq 'ARRAY' ? @$bind : $bind);
136             }
137              
138 16     16 0 58 return $self;
139             }
140 16         23  
  16         82  
141 16 100       62 my ($self, $other) = @_;
142 11 100       19  
  11         67  
143             if ( !@{$self->{sql}} ) {
144             if ( !@{$other->{sql}} ) {
145 16         36 return SQL::Maker::Condition->new;
146             }
147             return SQL::Maker::Condition->new(
148             sql => ['(' . $other->as_sql() . ')'],
149 5     5 0 15 bind => [@{$other->{bind}}],
150             );
151 5 100       9 }
  5         19  
152 2 100       4 if ( !@{$other->{sql}} ) {
  2         7  
153 1         5 return SQL::Maker::Condition->new(
154             sql => ['(' . $self->as_sql() . ')'],
155             bind => [@{$self->{bind}}],
156             );
157 1         4 }
  1         5  
158              
159             return SQL::Maker::Condition->new(
160 3 100       7 sql => ['(' . $self->as_sql() . ') AND (' . $other->as_sql() . ')'],
  3         17  
161             bind => [@{$self->{bind}}, @{$other->{bind}}],
162             );
163 1         5 }
  1         5  
164              
165             my ($self, $other) = @_;
166              
167             if ( !@{$self->{sql}} ) {
168             if ( !@{$other->{sql}} ) {
169 2         9 return SQL::Maker::Condition->new;
  2         6  
  2         9  
170             }
171             return SQL::Maker::Condition->new(
172             sql => ['(' . $other->as_sql() . ')'],
173             bind => [@{$other->{bind}}],
174 4     4 0 12 );
175             }
176 4 100       8 if ( !@{$other->{sql}} ) {
  4         16  
177 2 100       4 return SQL::Maker::Condition->new(
  2         7  
178 1         4 sql => ['(' . $self->as_sql() . ')'],
179             bind => [@{$self->{bind}}],
180             );
181             }
182 1         4  
  1         5  
183             # return value is enclosed with '()'.
184             # because 'OR' operator priority less than 'AND'.
185 2 100       6 return SQL::Maker::Condition->new(
  2         9  
186             sql => ['((' . $self->as_sql() . ') OR (' . $other->as_sql() . '))'],
187             bind => [@{$self->{bind}}, @{$other->{bind}}],
188 1         5 );
  1         5  
189             }
190              
191             my ($self) = @_;
192             return join(' AND ', @{$self->{sql}});
193             }
194              
195             my $self = shift;
196 1         5 return wantarray ? @{$self->{bind}} : $self->{bind};
  1         3  
  1         4  
197             }
198              
199             1;
200              
201 288     288 0 677 =for test_synopsis
202 288         945 my ($sql, @bind);
  288         1055  
203              
204             =head1 NAME
205              
206 281     281 0 27939 SQL::Maker::Condition - condition object for SQL::Maker
207 281 50       584  
  281         994  
208             =head1 SYNOPSIS
209              
210             my $condition = SQL::Maker::Condition->new(
211             name_sep => '.',
212             quote_char => '`',
213             );
214             $condition->add('foo_id' => 3);
215             $condition->add('bar_id' => 4);
216             $sql = $condition->as_sql(); # (`foo_id`=?) AND (`bar_id`=?)
217             @bind = $condition->bind(); # (3, 4)
218              
219             # add_raw
220             my $condition = SQL::Maker::Condition->new(
221             name_sep => '.',
222             quote_char => '`',
223             );
224             $condition->add_raw('EXISTS(SELECT * FROM bar WHERE name = ?)' => ['john']);
225             $condition->add_raw('type IS NOT NULL');
226             $sql = $condition->as_sql(); # (EXISTS(SELECT * FROM bar WHERE name = ?)) AND (type IS NOT NULL)
227             @bind = $condition->bind(); # ('john')
228              
229             # composite and
230             my $other = SQL::Maker::Condition->new(
231             name_sep => '.',
232             quote_char => '`',
233             );
234             $other->add('name' => 'john');
235             my $comp_and = $condition & $other;
236             $sql = $comp_and->as_sql(); # ((`foo_id`=?) AND (`bar_id`=?)) AND (`name`=?)
237             @bind = $comp_and->bind(); # (3, 4, 'john')
238              
239             # composite or
240             my $comp_or = $condition | $other;
241             $sql = $comp_and->as_sql(); # ((`foo_id`=?) AND (`bar_id`=?)) OR (`name`=?)
242             @bind = $comp_and->bind(); # (3, 4, 'john')
243              
244              
245             =head1 CONDITION CHEAT SHEET
246              
247             Here is a cheat sheet for conditions.
248              
249             IN: ['foo','bar']
250             OUT QUERY: '`foo` = ?'
251             OUT BIND: ('bar')
252              
253             IN: ['foo',['bar','baz']]
254             OUT QUERY: '`foo` IN (?, ?)'
255             OUT BIND: ('bar','baz')
256              
257             IN: ['foo',{'IN' => ['bar','baz']}]
258             OUT QUERY: '`foo` IN (?, ?)'
259             OUT BIND: ('bar','baz')
260              
261             IN: ['foo',{'not IN' => ['bar','baz']}]
262             OUT QUERY: '`foo` NOT IN (?, ?)'
263             OUT BIND: ('bar','baz')
264              
265             IN: ['foo',{'!=' => 'bar'}]
266             OUT QUERY: '`foo` != ?'
267             OUT BIND: ('bar')
268              
269             IN: ['foo',\'IS NOT NULL']
270             OUT QUERY: '`foo` IS NOT NULL'
271             OUT BIND: ()
272              
273             IN: ['foo',{'between' => ['1','2']}]
274             OUT QUERY: '`foo` BETWEEN ? AND ?'
275             OUT BIND: ('1','2')
276              
277             IN: ['foo',{'like' => 'xaic%'}]
278             OUT QUERY: '`foo` LIKE ?'
279             OUT BIND: ('xaic%')
280              
281             IN: ['foo',[{'>' => 'bar'},{'<' => 'baz'}]]
282             OUT QUERY: '(`foo` > ?) OR (`foo` < ?)'
283             OUT BIND: ('bar','baz')
284              
285             IN: ['foo',['-and',{'>' => 'bar'},{'<' => 'baz'}]]
286             OUT QUERY: '(`foo` > ?) AND (`foo` < ?)'
287             OUT BIND: ('bar','baz')
288              
289             IN: ['foo',['-and','foo','bar','baz']]
290             OUT QUERY: '(`foo` = ?) AND (`foo` = ?) AND (`foo` = ?)'
291             OUT BIND: ('foo','bar','baz')
292              
293             IN: ['foo_id',\['IN (SELECT foo_id FROM bar WHERE t=?)',44]]
294             OUT QUERY: '`foo_id` IN (SELECT foo_id FROM bar WHERE t=?)'
295             OUT BIND: ('44')
296              
297             IN: ['foo_id', {IN => \['SELECT foo_id FROM bar WHERE t=?',44]}]
298             OUT QUERY: '`foo_id` IN (SELECT foo_id FROM bar WHERE t=?)'
299             OUT BIND: ('44')
300              
301             IN: ['foo_id',\['MATCH (col1, col2) AGAINST (?)','apples']]
302             OUT QUERY: '`foo_id` MATCH (col1, col2) AGAINST (?)'
303             OUT BIND: ('apples')
304              
305             IN: ['foo_id',undef]
306             OUT QUERY: '`foo_id` IS NULL'
307             OUT BIND: ()
308              
309             IN: ['foo_id',{'IN' => []}]
310             OUT QUERY: '0=1'
311             OUT BIND: ()
312              
313             IN: ['foo_id',{'NOT IN' => []}]
314             OUT QUERY: '1=1'
315             OUT BIND: ()
316              
317             IN: ['foo_id', [123,sql_type(\3, SQL_INTEGER)]]
318             OUT QUERY: '`foo_id` IN (?, ?)'
319             OUT BIND: (123, sql_type(\3, SQL_INTEGER))
320              
321             IN: ['foo_id', sql_type(\3, SQL_INTEGER)]
322             OUT QUERY: '`foo_id` = ?'
323             OUT BIND: sql_type(\3, SQL_INTEGER)
324              
325             IN: ['created_on', { '>', \'DATE_SUB(NOW(), INTERVAL 1 DAY)' }]
326             OUT QUERY: '`created_on` > DATE_SUB(NOW(), INTERVAL 1 DAY)'
327             OUT BIND:
328              
329             It is also possible to use the functions exported by C<SQL::QueryMaker> to define the conditions.
330              
331             IN: ['foo' => sql_in(['bar','baz'])]
332             OUT QUERY: '`foo` IN (?,?)'
333             OUT BIND: ('bar','baz')
334              
335             IN: ['foo' => sql_lt(3)]
336             OUT QUERY: '`foo` < ?'
337             OUT BIND: (3)
338              
339             IN: ['foo' => sql_not_in(['bar','baz'])]
340             OUT QUERY: '`foo` NOT IN (?,?)'
341             OUT BIND: ('bar','baz')
342              
343             IN: ['foo' => sql_ne('bar')]
344             OUT QUERY: '`foo` != ?'
345             OUT BIND: ('bar')
346              
347             IN: ['foo' => sql_is_not_null()]
348             OUT QUERY: '`foo` IS NOT NULL'
349             OUT BIND: ()
350              
351             IN: ['foo' => sql_between('1','2')]
352             OUT QUERY: '`foo` BETWEEN ? AND ?'
353             OUT BIND: ('1','2')
354              
355             IN: ['foo' => sql_like('xaic%')]
356             OUT QUERY: '`foo` LIKE ?'
357             OUT BIND: ('xaic%')
358              
359             IN: ['foo' => sql_or([sql_gt('bar'), sql_lt('baz')])]
360             OUT QUERY: '(`foo` > ?) OR (`foo` < ?)'
361             OUT BIND: ('bar','baz')
362              
363             IN: ['foo' => sql_and([sql_gt('bar'), sql_lt('baz')])]
364             OUT QUERY: '(`foo` > ?) AND (`foo` < ?)'
365             OUT BIND: ('bar','baz')
366              
367             IN: ['foo_id' => sql_op('IN (SELECT foo_id FROM bar WHERE t=?)',[44])]
368             OUT QUERY: '`foo_id` IN (SELECT foo_id FROM bar WHERE t=?)'
369             OUT BIND: ('44')
370              
371             IN: ['foo_id' => sql_in([sql_raw('SELECT foo_id FROM bar WHERE t=?',44)])]
372             OUT QUERY: '`foo_id` IN ((SELECT foo_id FROM bar WHERE t=?))'
373             OUT BIND: ('44')
374              
375             IN: ['foo_id', => sql_op('MATCH (@) AGAINST (?)',['apples'])]
376             OUT QUERY: 'MATCH (`foo_id`) AGAINST (?)'
377             OUT BIND: ('apples')
378              
379             IN: ['foo_id',undef]
380             OUT QUERY: '`foo_id` IS NULL'
381             OUT BIND: ()
382              
383             IN: ['foo_id',sql_in([])]
384             OUT QUERY: '0=1'
385             OUT BIND: ()
386              
387             IN: ['foo_id',sql_not_in([])]
388             OUT QUERY: '1=1'
389             OUT BIND: ()
390              
391             IN: ['foo_id', sql_type(\3, SQL_INTEGER)]
392             OUT QUERY: '`foo_id` = ?'
393             OUT BIND: sql_type(\3, SQL_INTEGER)
394              
395             IN: ['foo_id', sql_in([sql_type(\3, SQL_INTEGER)])]
396             OUT QUERY: '`foo_id` IN (?)'
397             OUT BIND: sql_type(\3, SQL_INTEGER)
398              
399             IN: ['created_on', sql_gt(sql_raw('DATE_SUB(NOW(), INTERVAL 1 DAY)')) ]
400             OUT QUERY: '`created_on` > DATE_SUB(NOW(), INTERVAL 1 DAY)'
401             OUT BIND:
402              
403             =head1 SEE ALSO
404              
405             L<SQL::Maker>
406