File Coverage

blib/lib/SQL/Entity/Condition.pm
Criterion Covered Total %
statement 122 139 87.7
branch 55 74 74.3
condition 11 15 73.3
subroutine 23 26 88.4
pod 20 20 100.0
total 231 274 84.3


line stmt bran cond sub pod time code
1             package SQL::Entity::Condition;
2              
3 6     6   26416 use strict;
  6         13  
  6         212  
4 6     6   30 use warnings;
  6         11  
  6         210  
5 6     6   30 use vars qw(@EXPORT_OK %EXPORT_TAGS $VERSION);
  6         10  
  6         538  
6              
7             $VERSION = '0.01';
8              
9 6     6   56 use Carp;
  6         11  
  6         476  
10 6     6   35 use base 'Exporter';
  6         14  
  6         14340  
11              
12             @EXPORT_OK = qw(
13             sql_cond
14             sql_and
15             sql_or
16             );
17             %EXPORT_TAGS = (all => \@EXPORT_OK);
18              
19              
20             =head1 NAME
21              
22             SQL::Entity::Condition - Entity SQL Condition abstraction.
23              
24             =head1 DESCRIPTION
25              
26             Represents sql condition.
27              
28             =head1 SYNOPSIS
29              
30             use SQL::Entity::Condition ':all';
31              
32             #Creates "a = 1" condition
33             my $cond = sql_cond('a', '=', 1);
34              
35             #Creates "a = 1 AND b > 1 AND c < 1" condition
36             my $cond = sql_and(
37             sql_cond('a', '=', 1),
38             sql_cond('b', '>', 1),
39             sql_cond('c', '<', 1),
40             );
41              
42             Creates "a = 1 OR b > 1 OR c < 1" condition
43             my $cond = sql_or(
44             sql_cond('a', '=', 1),
45             sql_cond('b', '>', 1),
46             sql_cond('c', '<', 1),
47             );
48              
49             Creates "(a = 1 AND b = 1) OR c LIKE 1" condition
50             my $cond = sql_and(
51             sql_cond('a', '=', 1),
52             sql_cond('b', '=', 1),
53             )->or(
54             sql_cond('c', 'LIKE', 1)
55             );
56              
57             =head2 EXPORT
58              
59             None by default.
60             sql_cond
61             sql_and
62             sql_or
63             by tag 'all'
64              
65             =head2 ATTRIBUTES
66              
67             =over
68              
69             =item operand1
70              
71             First operand for condition.
72              
73             =cut
74              
75 46     46 1 86 sub operand1 {shift->{operand1}}
76              
77              
78             =item set_operand1
79              
80             Sets the first condition operand .
81              
82             =cut
83              
84              
85             sub set_operand1 {
86 0     0 1 0 my ($self, $value) = @_;
87 0         0 $self->{operand1} = $value;
88             }
89              
90              
91             =item operator
92              
93             Operator
94              
95             =cut
96              
97 84     84 1 279 sub operator {shift->{operator}}
98              
99              
100             =item operand2
101              
102             Second operand for condition.
103              
104             =cut
105              
106 46     46 1 75 sub operand2 {shift->{operand2}}
107              
108              
109             =item set_operand2
110              
111             Sets the secound condition operand .
112              
113             =cut
114              
115             sub set_operand2 {
116 0     0 1 0 my ($self, $value) = @_;
117 0         0 $self->{operand2} = $value;
118             }
119              
120              
121             =item relation
122              
123             Relation between compsite condition.
124              
125             =cut
126              
127             sub relation {
128 88     88 1 99 my ($self, $value) = @_;
129 88 100       671 $self->{relation} = $value if defined $value;
130 88         287 $self->{relation};
131             }
132              
133              
134             =item conditions
135              
136             Association to composie condition
137              
138             =cut
139              
140             sub conditions {
141 74     74 1 87 my ($self) = @_;
142 74   100     313 $self->{conditions} ||= [];
143             }
144              
145              
146              
147             =back
148              
149             =head2 METHOD
150              
151             =over
152              
153             =item new
154              
155             =cut
156              
157             {
158             my @attributes = qw(operand1 operator operand2 relation conditions);
159             sub new {
160 42     42 1 132 my ($class, %args) = @_;
161 42         112 for my $attribute (keys %args) {
162 100 50       130 confess "inknown attribute: $attribute" unless grep { $attribute eq $_} @attributes;
  500         902  
163             }
164 42         336 bless {%args}, $class;
165             }
166              
167              
168             =item condition_iterator
169              
170             =cut
171              
172             my $dynamic_condition = __PACKAGE__->new;
173             sub condition_iterator {
174 15     15 1 22 my ($slef) = @_;
175 15         15 my @conditions = @{$slef->conditions};
  15         35  
176 15         21 my $i = 0;
177             sub {
178 36     36   46 my $result = $conditions[$i++];
179 36 100       82 if(ref($result) eq 'HASH') {
180 4         24 $dynamic_condition->{$_} = $result->{$_} for @attributes;
181 4         5 $result = $dynamic_condition;
182             }
183 36         126 $result;
184             }
185 15         63 }
186              
187             }
188              
189             =item sql_cond( string | SQL::Entity::Column: $operand1, string: $operator, string | SQL::Entity::Column: $operand2) returns SQL::Entity::Condition
190              
191             Create simple Condition object.
192              
193             =cut
194              
195             sub sql_cond {
196 32     32 1 3612 my ($op1, $op, $op2) = @_;
197 32         114 SQL::Entity::Condition->new(
198             operand1 => $op1,
199             operator => $op,
200             operand2 => $op2,
201             );
202             }
203              
204              
205             =item and
206              
207             Create a composite Condition object with AND relation between current object and
208             passed in condition list.
209              
210             =cut
211              
212             sub and {
213 8     8 1 13 my $self = shift;
214 8 100       18 $self->relation('AND')
215             unless $self->relation;
216 8 100       14 if($self->relation eq 'AND') {
217 7         6 push @{$self->conditions}, @_;
  7         17  
218             } else {
219 1         2 return sql_composite('AND',$self, @_);
220             }
221 7         23 $self;
222             }
223              
224              
225             =item or
226              
227             Create a composite Condition object with OR relation between current object an
228             passed in condition list.
229              
230             =cut
231              
232             sub or {
233 3     3 1 4 my $self = shift;
234 3 100       7 $self->relation('OR')
235             unless $self->relation;
236              
237 3 100       4 if($self->relation eq 'OR') {
238 2         4 push @{$self->conditions}, @_;
  2         3  
239             } else {
240 1         4 return sql_composite('OR',$self, @_);
241             }
242 2         6 $self;
243             }
244              
245              
246             =item sql_and
247              
248             Create a composite Condition object with AND relation.
249              
250             =cut
251              
252             sub sql_and {
253 1     1 1 4 unshift @_, 'AND';
254 1         4 &sql_composite;
255             }
256              
257              
258             =item sql_or
259              
260             Create a composite Condition object with OR relation.
261              
262             =cut
263              
264             sub sql_or {
265 1     1 1 3 unshift @_, 'OR';
266 1         2 &sql_composite;
267             }
268              
269              
270             =item sql_composite
271              
272             Create a composite Condition object.
273              
274             =cut
275              
276             sub sql_composite {
277 4     4 1 10 my ($relation, @args) = @_;
278 4         10 my $composite_condition = SQL::Entity::Condition->new( relation => $relation );
279 4         7 push @{$composite_condition->conditions}, @args;
  4         8  
280 4         15 $composite_condition;
281             }
282              
283              
284              
285             =item as_string
286              
287             Converts condition to string
288              
289             =cut
290              
291             sub as_string {
292 46     46 1 496 my ($self, $columns, $bind_variables, $entity, $join_methods) = @_;
293 46   100     118 $columns ||= {};
294 46         92 my $conditions = $self->conditions;
295 46         51 my $result;
296 46         98 my $operand1 = $self->operand1;
297 46         89 my $operand2 = $self->operand2;
298 46         57 my $column = '';
299            
300 46 100       92 if(ref($operand1) eq 'SQL::Entity::Column') {
301 3         5 $column = $operand1;
302            
303             } else {
304 43 100 100     187 $column = $operand1 && $columns->{$operand1} ? $columns->{$operand1} : undef;
305             }
306            
307 46   66     129 my $case_insensitive = ($column && ! $column->case_sensitive);
308 46 50 66     277 my $subquery = ($column && ref($column) eq 'SQL::Entity::Column::SubQuery') ? 1 : 0;
309 46 50       95 $result .= " EXISTS(" if $subquery;
310            
311 46 50       136 $result .= $operand1
    50          
    50          
    100          
312             ? operand($operand1, $columns, undef, $entity, $join_methods, $operand2)
313             . ($self->operator ? " " . $self->operator . " ": "")
314             . ($case_insensitive ? 'UPPER(' : '')
315             . operand($operand2, $columns, $bind_variables, $entity, $join_methods, $operand1)
316             . ($case_insensitive ? ')' : '')
317             : '';
318 46 50       105 $result .= ") " if $subquery;
319            
320 46 100       80 if(@$conditions) {
321 15         28 my $relation = $self->relation;
322 15         30 my $iterator = $self->condition_iterator;
323 15         32 while(my $condition = $iterator->()) {
324 21 100       57 $result .= ($result ? " $relation " : "")
    100          
    100          
325             . ($condition->relation ? "(" : '')
326             . $condition->as_string($columns, $bind_variables, $entity, $join_methods)
327             . ($condition->relation ? ")" : "" );
328             }
329             }
330            
331 46         250 $result;
332             }
333              
334              
335             =item operand
336              
337             Return expression for passed in operand.
338              
339             =cut
340              
341             sub operand {
342 84     84 1 129 my ($operand, $columns, $bind_variables, $entity, $join_methods, $reflective_operand) = @_;
343 84         101 my $result = $operand;
344 84 50       154 return '' unless defined $operand;
345            
346 84 100       292 if (ref($operand) eq 'SQL::Entity::Column') {
    100          
    50          
    100          
347 6         20 $result = $operand->as_operand;
348 6         97 $entity->set_relationship_join_method($operand, 'JOIN', $join_methods);
349 6 50       42 die "column ". $operand->as_string ." cant be queried "
350             unless $operand->queryable;
351            
352             } elsif (ref($bind_variables) eq 'ARRAY') {
353 12 100       32 if (ref($operand)) {
354 1         3 push @$bind_variables, @$operand;
355 1         2 $result = "(" . (join ",", map {'?'} @$operand) . ")";
  3         5  
356             } else {
357 11         23 push @$bind_variables, $operand;
358 11         18 $result = '?';
359             }
360             } elsif (ref($bind_variables) eq 'HASH') {
361 0 0       0 if (ref($operand)) {
362 0         0 $result = "(" . (join ",",
363 0         0 map { (':' . extend_bind_variables($bind_variables, $_, $reflective_operand)) } @$operand) . ")";
364            
365             } else {
366 0         0 $result = ':' . extend_bind_variables($bind_variables, $operand, $reflective_operand);
367             }
368            
369             } elsif (my $column = $columns->{$operand}) {
370 7 50       27 die "column ". $column->as_string ." cant be queried "
371             unless $column->queryable;
372 7         99 $entity->set_relationship_join_method($column, 'JOIN', $join_methods);
373 7         41 $result = $column->as_operand;
374            
375             } else {
376 59 100       74 if(ref($operand) eq 'ARRAY') {
377 1         3 $result = "(" . join(",", @$operand). ")";
378            
379             } else {
380 58         71 $result = $operand;
381             }
382             }
383 84         365 $result;
384             }
385              
386              
387             =item extend_bind_variables
388              
389             =cut
390              
391             sub extend_bind_variables {
392 0     0 1 0 my ($bind_variables, $value, $column, $counter) = @_;
393 0 0       0 my $column_name = (ref $column) ? $column->name : $column;
394 0         0 my $result = $column_name;
395 0   0     0 $counter ||= 0;
396 0 0       0 if (exists $bind_variables->{$result}) {
397 0         0 $result = $column_name . ($counter++);
398 0 0       0 extend_bind_variables($bind_variables, $value, $column, $counter)
399             if (exists $bind_variables->{$result});
400             }
401 0         0 $bind_variables->{$result} = $value;
402 0         0 $result;
403             }
404              
405              
406             =item struct_to_condition
407              
408             Converts passed in data structure to condition object.
409             SQL::Entity::Condition->struct_to_condition(a => 1, b => 3);
410             converts to a = 1 AND b = 3
411              
412             SQL::Entity::Condition->struct_to_condition(a => 1, b => [1,3]);
413             converts to a = 1 AND b IN (1,3)
414              
415             SQL::Entity::Condition->struct_to_condition(a => 1, b => {operator => '!=', operand => 3});
416             converts to a = 1 AND b != 3
417              
418             SQL::Entity::Condition->struct_to_condition(a => 1, b => {operator => 'LIKE', operand => "'A%'", relation => 'OR'});
419             coverts to a = 1 OR b LIKE 'A%'
420              
421             =cut
422              
423             sub struct_to_condition {
424 12     12 1 1809 my ($class, @args) = @_;
425 12         18 my $result;
426 12         39 for (my $i = 0; $i < $#args; $i+=2) {
427 14         36 my ($operator, $operand, $relation) = convert_extended_operand($args[$i + 1]);
428 14 100       31 unless($result) {
429 10         27 $result = sql_cond($args[$i], $operator, $operand);
430             } else {
431 4         16 $result = $result->$relation({operand1 => $args[$i], operator => $operator, operand2 => $operand});
432             }
433             }
434 12         35 $result;
435             }
436              
437              
438              
439             =item convert_extended_operand
440              
441             Return operator, operand2, relation for passed in operand
442              
443             =cut
444              
445             sub convert_extended_operand {
446 14     14 1 60 my ($operand) = @_;
447 14         172 my $operator = '=';
448 14         16 my $relation = 'and';
449 14         22 my $operand_type = ref($operand);
450 14 100       45 if($operand_type eq 'ARRAY') {
    100          
451 1         2 $operator = 'IN';
452             } elsif($operand_type eq 'HASH') {
453 2 100       6 $relation = $operand->{relation} if $operand->{relation};
454 2         3 $operator = $operand->{operator};
455 2         2 $operand = $operand->{operand};
456             }
457 14         44 ($operator, $operand, lc $relation);
458             }
459              
460             1;
461              
462             __END__