File Coverage

blib/lib/Class/ReluctantORM/SQL/Expression/FunctionCall.pm
Criterion Covered Total %
statement 33 118 27.9
branch 0 30 0.0
condition 0 5 0.0
subroutine 11 23 47.8
pod 9 9 100.0
total 53 185 28.6


line stmt bran cond sub pod time code
1             package Class::ReluctantORM::SQL::Expression::FunctionCall;
2              
3             =head1 NAME
4              
5             Class::ReluctantORM::SQL::Expression::FunctionCall - Represent a function call
6              
7             =head1 SYNOPSIS
8              
9             # Save yourself some typing
10             use Class::ReluctantORM::SQL::Aliases;
11              
12             # This creates "REPLACE(mycol,'old','new')"
13             my $fc0 = FunctionCall->new(
14             'replace',
15             Column->new(column =>'mycol',
16             'old',
17             'new',
18             );
19              
20             # Same thing
21             my $fc0 = FunctionCall->new(
22             Function->by_name('replace'),
23             Column->new(column =>'mycol',
24             'old',
25             'new',
26             );
27              
28             # This creates '1=1'
29             my $fc1 = FunctionCall->new('=', 1, 1);
30              
31             # This creates 'my_column = ?'
32             my $fc2 = FunctionCall->new(
33             '=',
34             Column->new(column =>'my_column',
35             Param->new(),
36             );
37              
38             # Wrap $fc2 in NOT ( 'NOT my_column = ?' )
39             my $fc3 = FunctionCall->new('NOT', $fc2);
40              
41             # Make '(1=1) AND (NOT (my_column = ?))'
42             my $fc4 = FunctionCall->new('AND', $fc1, $fc3);
43              
44             # Dump a FunctionCall as a string (for diagnostics only - NOT RBMS safe)
45             my $str = $fc->pretty_print(); # Verbose
46             my $str = $fc->pretty_print(one_line => 1);
47              
48             =head1 DESCRIPTION
49              
50             Represents an actual call to a function, operator, or stored procedure. Contains a single Function, and zero
51             or more Expressions that are used as arguments to the Function.
52              
53             FunctionCalls are themselves Expressions, so they can be composed (nested).
54              
55             =cut
56              
57 1     1   5 use strict;
  1         3  
  1         33  
58 1     1   6 use warnings;
  1         1  
  1         23  
59              
60 1     1   5 use Data::Dumper;
  1         3  
  1         55  
61 1     1   6 use Scalar::Util qw(blessed);
  1         1  
  1         55  
62 1     1   6 use Class::ReluctantORM::Exception;
  1         1  
  1         22  
63 1     1   6 use Class::ReluctantORM::Utilities qw(install_method);
  1         2  
  1         61  
64             our $DEBUG = 0;
65              
66 1     1   5 use Class::ReluctantORM::SQL::Aliases;
  1         2  
  1         122  
67 1     1   7 use Class::ReluctantORM::SQL::Function;
  1         1  
  1         9  
68 1     1   767 use Class::ReluctantORM::SQL::Expression::Literal;
  1         4  
  1         11  
69 1     1   23 use Class::ReluctantORM::SQL::Expression;
  1         2  
  1         7  
70              
71 1     1   35 use base 'Class::ReluctantORM::SQL::Expression';
  1         2  
  1         1344  
72              
73             =head1 CONSTRUCTORS
74              
75             =cut
76              
77             =head2 $fc = SQL::Expression::FunctionCall->new($func, $exp1, [$exp2, ...]);
78              
79             =head2 $fc = SQL::Expression::FunctionCall->new($funcname, $exp1, [$exp2,...]);
80              
81             Creates a new FunctionCall. The Function to be called
82             must be specified, as well as all input expressions.
83              
84             In the first form, the Function to be called is provided directly.
85              
86             In the second form, the Function is specified by name, and a Function->by_name lookup is made on your behalf.
87              
88             An exception will be thrown if the number of arguments does not
89             match the operator's arity.
90              
91             $expN is either a Class::ReluctantORM::SQL::Expression
92             subclass, or a plain scalar, or undef. Scalars and undefs will
93             be "autoboxed" into being Class::ReluctantORM::SQL::Expression::Literal
94             objects, with undefs becoming NULLs.
95              
96             =cut
97              
98             sub new {
99 0     0 1   my $class = shift;
100 0           my $func = shift;
101 0           my @exps = @_;
102              
103 0 0         if (!ref($func)) {
    0          
104 0           $func = Function->by_name($func);
105             } elsif (!$func->isa(Function)) {
106 0           Class::ReluctantORM::Exception::Param::WrongType->croak(
107             param => 'function',
108             value => $func,
109             error => "The first arg to new() must be either the name of a Function or a Function object",
110             expected => 'Class::ReluctantORM::SQL::Function',
111             );
112             }
113              
114 0           my $funcname = $func->name();
115 0 0         if (defined $func->max_inputs()) {
116 0 0         unless (@exps <= $func->max_inputs()) {
117 0           Class::ReluctantORM::Exception::Param::Spurious->croak(error => "The '$funcname' operator allows at most " . $func->max_inputs() . " arguments");
118             }
119             }
120 0 0         unless (@exps >= $func->min_inputs()) {
121 0           Class::ReluctantORM::Exception::Param::Missing->croak(error => "The '$funcname' operator requires at least " . $func->min_inputs() . " arguments");
122             }
123              
124 0           my @boxed_exps = ();
125 0           foreach my $exp (@exps) {
126 0 0 0       if (!ref($exp)) {
    0          
127 0           push @boxed_exps, Literal->new($exp);
128             } elsif (! (blessed($exp) && $exp->isa('Class::ReluctantORM::SQL::Expression'))) {
129 0           Class::ReluctantORM::Exception::Param::WrongType->croak(
130             error => "FunctionCall input expressions must be either plain scalars or Expressions",
131             param => 'exp',
132             value => $exp,
133             expected => 'Class::ReluctantORM::SQL::Expression',
134             );
135             } else {
136 0           push @boxed_exps, $exp;
137             }
138             }
139              
140 0           my $self = {
141             function => $func,
142             exps => \@boxed_exps,
143             };
144 0           bless $self, $class;
145              
146 0           foreach my $exp (@boxed_exps) { $exp->parent_expression($self); }
  0            
147              
148 0           return $self;
149             }
150              
151             =head2 $clone = $fc->clone();
152              
153             Makes a new FunctionCall. The arguments of the original are deeply cloned. The Function itself will be re-used, since each Function type is a singleton.
154              
155             =cut
156              
157             sub clone {
158 0     0 1   my $self = shift;
159 0           my $class = ref $self;
160              
161 0           my @cloned_args = map { $_->clone(); } $self->arguments();
  0            
162              
163 0           my $other = $class->new(
164             $self->function(),
165             @cloned_args,
166             );
167              
168 0           return $other;
169             }
170              
171              
172             =head1 ACCESSORS
173              
174             =cut
175              
176              
177             =head2 @exps = $fc->arguments();
178              
179             Returns the input expressions of the function call.
180              
181             =cut
182              
183 0     0 1   sub arguments { return @{shift->{exps}}; }
  0            
184              
185             =head2 @exps = $fc->child_expressions();
186              
187             Returns the child nodes of this node (same as arguments()). Required by the Expression interface.
188              
189             =cut
190              
191 0     0 1   sub child_expressions { return shift->arguments(); }
192              
193             =head2 $bool = $arg->is_function_call();
194              
195             All objects of this class return true. The class adds this method to Expression, making all other subclasses of it return false.
196              
197             =cut
198              
199 0     0     install_method('Class::ReluctantORM::SQL::Expression', 'is_function_call', sub { return 0; });
200 0     0 1   sub is_function_call { return 1; }
201              
202              
203             =head2 $bool = $fc->is_leaf_expression();
204              
205             Returns true if the number of arguments to the function call are zero. Required by the Expression interface.
206              
207             =cut
208              
209 0     0 1   sub is_leaf_expression { return (shift->arguments() == 0); }
210              
211              
212             =head2 $func = $fc->function();
213              
214             Returns the Function being referred to by the FunctionCall.
215              
216             =cut
217              
218 0     0 1   sub function { return shift->{function}; }
219              
220             =head2 $str = $fc->pretty_print();
221              
222             Renders a human-readable representation of the FunctionCall.
223              
224             =cut
225              
226             sub pretty_print {
227 0     0 1   my $self = shift;
228 0           my %args = @_;
229 0 0         if ($args{one_line}) {
230 0           return $self->__pp_brief(%args);
231             } else {
232 0           return $self->__pp_verbose(%args);
233             }
234             }
235              
236             sub __pp_brief {
237 0     0     my $self = shift;
238 0           my %args = @_;
239 0           my $str = '';
240 0           $str .= '(';
241 0           $str .= join ',', (
242             ("'" . $self->function->name . "'"),
243 0           map { $_->pretty_print(%args) } $self->arguments()
244             );
245 0           $str .= ')';
246 0           return $str;
247             }
248              
249             sub __pp_verbose {
250 0     0     my $self = shift;
251 0           my %args = @_;
252 0   0       my $prefix = $args{prefix} || '';
253 0           my $str = $prefix . "FUNCTION_CALL '" . $self->function->name . "'\n";
254 0           my @args = $self->arguments;
255 0           my $last_arg = pop @args;
256 0           foreach my $arg (@args) {
257 0           $str .= $arg->pretty_print(%args, prefix => $prefix . ' | ');
258             }
259 0           $str .= $last_arg->pretty_print(%args, prefix => $prefix . ' ` ');
260 0           return $str;
261             }
262              
263             =begin devdocs
264              
265             =head2 $bool = $fc->is_equivalent($expr);
266              
267             Returns true if the two criteria are certainly equivalent (does not check table or column aliases).
268              
269             Returns false otherwise.
270              
271             Buggy - has false negatives, but no known false positives.
272              
273             =cut
274              
275             sub is_equivalent {
276 0     0 1   my $ca = shift;
277 0           my $cb = shift;
278              
279 0 0         unless ($cb->is_function_call()) { return 0; }
  0            
280 0 0         unless ($ca->function->name eq $cb->function->name) { return 0; }
  0            
281              
282 0 0         if ($ca->function->is_associative()) {
283             # TODO - massaging (ie, flatten AND trees?)
284             }
285              
286 0           my @args_a = $ca->arguments();
287 0           my @args_b = $cb->arguments();
288 0 0         unless (@args_a == @args_b) { return 0; }
  0            
289              
290 0 0         if (!$ca->function->is_commutative()) {
291             # order must match
292 0           for my $i (0..$#args_a) {
293 0 0         unless ($args_a[$i]->is_equivalent($args_b[$i])) {
294 0           return 0;
295             }
296             }
297             } else {
298             # Ugggggg this is a terrible approach
299 0           my (%unmatched_a, %unmatched_b);
300 0           for my $i (0..$#args_a) {
301 0           $unmatched_a{$i} = $args_a[$i];
302             }
303              
304             ARG_B:
305 0           foreach my $arg_b (@args_b) {
306 0           foreach my $arg_a_idx (keys %unmatched_a) {
307 0           my $arg_a = $unmatched_a{$arg_a_idx};
308 0 0         if ($arg_a->is_equivalent($arg_b)) {
309 0           delete $unmatched_a{$arg_a_idx};
310 0           next ARG_B;
311             }
312             }
313             # If we get here, no match for arg_b
314 0           return 0;
315             }
316             }
317              
318 0           return 1;
319              
320             }
321              
322              
323              
324              
325             =head1 AUTHOR
326              
327             Clinton Wolfe January 2010
328              
329             =cut
330              
331             1;