File Coverage

blib/lib/Alzabo/SQLMaker.pm
Criterion Covered Total %
statement 78 1140 6.8
branch 16 654 2.4
condition 15 703 2.1
subroutine 14 271 5.1
pod 20 246 8.1
total 143 3014 4.7


line stmt bran cond sub pod time code
1             package Alzabo::SQLMaker;
2              
3 11     11   66 use strict;
  11         23  
  11         495  
4 11     11   59 use vars qw($VERSION $AUTOLOAD);
  11         27  
  11         599  
5              
6 11     11   58 use Alzabo::Exceptions;
  11         20  
  11         213  
7 11     11   67 use Alzabo::Utils;
  11         20  
  11         299  
8              
9 11     11   64 use Class::Factory::Util;
  11         24  
  11         96  
10 11     11   394 use Params::Validate qw( :all );
  11         22  
  11         11187  
11             Params::Validate::validation_options( on_fail => sub { Alzabo::Exception::Params->throw( error => join '', @_ ) } );
12              
13             $VERSION = 2.0;
14              
15             1;
16              
17             sub make_function
18             {
19 214     214 0 365 my $class = caller;
20              
21 214         13555 my %p =
22             validate( @_,
23             { function => { type => SCALAR },
24             min => { type => SCALAR, optional => 1 },
25             max => { type => UNDEF | SCALAR, optional => 1 },
26             groups => { type => ARRAYREF },
27             quote => { type => ARRAYREF, optional => 1 },
28             format => { type => SCALAR, optional => 1 },
29             is_modifier => { type => SCALAR, default => 0 },
30             has_spaces => { type => SCALAR, default => 0 },
31             allows_alias => { type => SCALAR, default => 1 },
32             no_parens => { type => SCALAR, default => 0 },
33             } );
34              
35 214         2833 my $valid = '';
36 214 100 100     676 if ( $p{min} || $p{max} )
37             {
38 189         259 $valid .= 'validate_pos( @_, ';
39 189         568 $valid .= join ', ', ('1') x $p{min};
40             }
41              
42 214 100 100     1947 if ( defined $p{min} && defined $p{max} && $p{max} > $p{min} )
    100 100        
      100        
43             {
44 17         20 $valid .= ', ';
45 17         40 $valid .= join ', ', ('0') x ( $p{max} - $p{min} );
46             }
47             elsif ( exists $p{min} && ! defined $p{max} )
48             {
49 11         22 $valid .= ", ('1') x (\@_ - $p{min})";
50             }
51 214 100       375 $valid .= ' );' if $valid;
52              
53 214         544 my @args = "function => '$p{function}'";
54              
55 214 100 100     860 if ( ! defined $p{max} || $p{max} > 0 )
56             {
57 190         270 push @args, ' args => [@_]';
58             }
59              
60 214 100       405 if ( $p{format} )
61             {
62 2         4 push @args, " format => '$p{format}'";
63             }
64              
65 214 100       436 if ( $p{quote} )
66             {
67 189         473 my $quote .= ' quote => [';
68 189         201 $quote .= join ', ', @{ $p{quote} };
  189         479  
69 189         221 $quote .= ']';
70 189         279 push @args, $quote;
71             }
72              
73 214         338 for my $k ( qw( is_modifier has_spaces allows_alias no_parens ) )
74             {
75 856 100       1934 if ( $p{$k} )
76             {
77 217         411 push @args, " $k => 1";
78             }
79             }
80              
81 214         522 my $args = join ",\n", @args;
82              
83 214         593 my $code = <<"EOF";
84             sub ${class}::$p{function}
85             {
86             shift if defined \$_[0] && Alzabo::Utils::safe_isa( \$_[0], 'Alzabo::SQLMaker' );
87             $valid
88             return Alzabo::SQLMaker::Function->new( $args );
89             }
90             EOF
91              
92 214 0 0 0 0 28051 eval $code;
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0 0 0 0 0    
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
93              
94             {
95 11     11   79 no strict 'refs';
  11         21  
  11         3586  
  214         361  
96 214         247 push @{ "$class\::EXPORT_OK" }, $p{function};
  214         811  
97 214         252 my $exp = \%{ "$class\::EXPORT_TAGS" };
  214         560  
98 214         268 foreach ( @{ $p{groups} } )
  214         507  
99             {
100 227         261 push @{ $exp->{$_} }, $p{function};
  227         831  
101             }
102 214         302 push @{ $exp->{all} }, $p{function};
  214         1654  
103             }
104             }
105              
106             sub load
107             {
108 0     0 1   shift;
109 0           my %p = @_;
110              
111 0           my $class = "Alzabo::SQLMaker::$p{rdbms}";
112 0           eval "use $class";
113 0 0         Alzabo::Exception::Eval->throw( error => $@ ) if $@;
114              
115 0           $class->init(@_);
116              
117 0           return $class;
118             }
119              
120 0     0 1   sub available { __PACKAGE__->subclasses }
121              
122             sub init
123             {
124 0     0 0   1;
125             }
126              
127 11         18939 use constant NEW_SPEC => { driver => { isa => 'Alzabo::Driver' },
128             quote_identifiers => { type => BOOLEAN,
129             default => 0 },
130 11     11   68 };
  11         24  
131              
132             sub new
133             {
134 0     0 1   my $class = shift;
135 0           my %p = validate( @_, NEW_SPEC );
136              
137 0           return bless { last_op => undef,
138             expect => undef,
139             type => undef,
140             sql => '',
141             bind => [],
142             placeholders => [],
143             as_id => 'aaaaa10000',
144             alias_in_having => 1,
145             %p,
146             }, $class;
147             }
148              
149             # this just needs to be some unique thing that won't ever look like a
150             # valid bound parameter
151             my $placeholder = do { my $x = 1; bless \$x, 'Alzabo::SQLMaker::Placeholder' };
152 0     0 0   sub placeholder { $placeholder }
153              
154             sub last_op
155             {
156 0     0 0   return shift->{last_op};
157             }
158              
159             sub select
160             {
161 0     0 1   my $self = shift;
162              
163 0 0         Alzabo::Exception::Params->throw( error => "The select method requires at least one parameter" )
164             unless @_;
165              
166 0           $self->{sql} .= 'SELECT ';
167              
168 0 0         if ( lc $_[0] eq 'distinct' )
169             {
170 0           $self->{sql} .= ' DISTINCT ';
171 0           shift;
172             }
173              
174 0           my @sql;
175 0           foreach my $elt (@_)
176             {
177 0 0         if ( Alzabo::Utils::safe_can( $elt, 'table' ) )
    0          
    0          
    0          
178             {
179 0           my $table = $elt->table;
180              
181 0           $self->{column_tables}{"$table"} = 1;
182              
183 0 0         my $sql =
184             ( $self->{quote_identifiers} ?
185             $self->{driver}->quote_identifier
186             ( $table->alias_name, $elt->name ) :
187             $table->alias_name . '.' . $elt->name );
188              
189 0 0         $sql .= ' AS ' .
190             ( $self->{quote_identifiers} ?
191             $self->{driver}->quote_identifier( $elt->alias_name ) :
192             $elt->alias_name );
193              
194 0           push @sql, $sql;
195             }
196             elsif ( Alzabo::Utils::safe_can( $elt, 'columns' ) )
197             {
198 0           $self->{column_tables}{"$elt"} = 1;
199              
200 0           my @cols;
201              
202 0           foreach my $col ( $elt->columns )
203             {
204 0 0         my $sql =
205             ( $self->{quote_identifiers} ?
206             $self->{driver}->quote_identifier
207             ( $elt->alias_name, $col->name ) :
208             $elt->alias_name . '.' . $col->name );
209              
210 0 0         $sql .= ' AS ' .
211             ( $self->{quote_identifiers} ?
212             $self->{driver}->quote_identifier( $elt->alias_name ) :
213             $elt->alias_name );
214              
215 0           push @cols, $sql;
216             }
217              
218 0           push @sql, join ', ', @cols;
219             }
220             elsif ( Alzabo::Utils::safe_isa( $elt, 'Alzabo::SQLMaker::Function' ) )
221             {
222 0           my $string = $elt->as_string( $self->{driver}, $self->{quote_identifiers} );
223              
224 0 0         if ( $elt->allows_alias )
225             {
226 0           push @sql, " $string AS " . $self->{as_id};
227 0           $self->{functions}{$string} = $self->{as_id};
228 0           ++$self->{as_id};
229             }
230             else
231             {
232 0           push @sql, $string;
233             }
234             }
235             elsif ( ! ref $elt )
236             {
237 0           push @sql, $elt;
238             }
239             else
240             {
241 0           Alzabo::Exception::SQL->throw
242             ( error => 'Arguments to select must be either column objects,' .
243             ' table objects, function objects, or plain scalars' );
244             }
245             }
246              
247 0           $self->{sql} .= join ', ', @sql;
248              
249 0           $self->{type} = 'select';
250 0           $self->{last_op} = 'select';
251              
252 0           return $self;
253             }
254              
255             sub from
256             {
257 0     0 1   my $self = shift;
258              
259 0           $self->_assert_last_op( qw( select delete function ) );
260              
261 0 0         my $spec =
262             $self->{last_op} eq 'select' ? { type => OBJECT | ARRAYREF } : { can => 'alias_name' };
263              
264 0           validate_pos( @_, ( $spec ) x @_ );
265              
266 0           $self->{sql} .= ' FROM ';
267              
268 0 0         if ( $self->{last_op} eq 'delete' )
269             {
270 0 0         $self->{sql} .=
271 0           join ', ', map { ( $self->{quote_identifiers} ?
272             $self->{driver}->quote_identifier( $_->name ) :
273             $_->name ) } @_;
274              
275 0           $self->{tables} = { map { $_ => 1 } @_ };
  0            
276             }
277             else
278             {
279 0           my $sql;
280              
281 0           $self->{tables} = {};
282              
283 0           my @plain;
284 0           foreach my $elt (@_)
285             {
286 0 0         if ( Alzabo::Utils::is_arrayref($elt) )
287             {
288 0 0         $sql .= ' ' if $sql;
289              
290 0           $sql .= $self->_outer_join(@$elt);
291             }
292             else
293             {
294 0           push @plain, $elt;
295             }
296             }
297              
298 0           foreach my $elt ( grep { ! exists $self->{tables}{$_ } } @plain )
  0            
299             {
300 0 0         $sql .= ', ' if $sql;
301              
302 0 0         if ( $self->{quote_identifiers} )
303             {
304 0           $sql .=
305             ( $self->{driver}->quote_identifier( $elt->name ) .
306             ' AS ' .
307             $self->{driver}->quote_identifier( $elt->alias_name ) );
308             }
309             else
310             {
311 0           $sql .= $elt->name . ' AS ' . $elt->alias_name;
312             }
313              
314 0           $self->{tables}{$elt} = 1;
315             }
316              
317 0           $self->{sql} .= $sql;
318             }
319              
320 0 0         if ($self->{type} eq 'select')
321             {
322 0           foreach my $t ( keys %{ $self->{column_tables} } )
  0            
323             {
324 0 0         unless ( $self->{tables}{$t} )
325             {
326 0           my $err = 'Cannot select column ';
327 0           $err .= 'unless its table is included in the FROM clause';
328 0           Alzabo::Exception::SQL->throw( error => $err );
329             }
330             }
331             }
332              
333 0           $self->{last_op} = 'from';
334              
335 0           return $self;
336             }
337              
338 11         45924 use constant _OUTER_JOIN_SPEC => ( { type => SCALAR },
339             ( { can => 'alias_name' } ) x 2,
340             { type => UNDEF | ARRAYREF | OBJECT, optional => 1 },
341             { type => UNDEF | ARRAYREF, optional => 1 },
342 11     11   132 );
  11         31  
343              
344             sub _outer_join
345             {
346 0     0     my $self = shift;
347              
348 0           my $tables = @_ - 1;
349 0           validate_pos( @_, _OUTER_JOIN_SPEC );
350              
351 0           my $type = uc shift;
352              
353 0           my $join_from = shift;
354 0           my $join_on = shift;
355 0           my $fk;
356 0 0 0       $fk = shift if $_[0] && Alzabo::Utils::safe_isa( $_[0], 'Alzabo::ForeignKey' );
357 0           my $where = shift;
358              
359 0 0         unless ($fk)
360             {
361 0           my @fk = $join_from->foreign_keys_by_table($join_on);
362              
363 0 0         Alzabo::Exception::Params->throw( error => "The " . $join_from->name . " table has no foreign keys to the " . $join_on->name . " table" )
364             unless @fk;
365              
366 0 0         Alzabo::Exception::Params->throw( error => "The " . $join_from->name . " table has more than 1 foreign key to the " . $join_on->name . " table" )
367             if @fk > 1;
368              
369 0           $fk = $fk[0];
370             }
371              
372 0           my $sql;
373 0 0         unless ( $self->{tables}{$join_from} )
374             {
375 0 0         $sql .=
376             ( $self->{quote_identifiers} ?
377             $self->{driver}->quote_identifier( $join_from->name ) :
378             $join_from->name );
379              
380 0           $sql .= ' AS ';
381 0 0         $sql .=
382             ( $self->{quote_identifiers} ?
383             $self->{driver}->quote_identifier( $join_from->alias_name ) :
384             $join_from->alias_name );
385             }
386              
387 0           $sql .= " $type OUTER JOIN ";
388              
389 0 0         $sql .= ( $self->{quote_identifiers} ?
390             $self->{driver}->quote_identifier( $join_on->name ) :
391             $join_on->name );
392              
393 0           $sql .= ' AS ';
394              
395 0 0         $sql .=
396             ( $self->{quote_identifiers} ?
397             $self->{driver}->quote_identifier( $join_on->alias_name ) :
398             $join_on->alias_name );
399              
400 0           $sql .= ' ON ';
401              
402 0 0         if ( $self->{quote_identifiers} )
403             {
404 0           $sql .=
405             ( join ' AND ',
406 0           map { $self->{driver}->quote_identifier
407             ( $join_from->alias_name, $_->[0]->name ) .
408             ' = ' .
409             $self->{driver}->quote_identifier
410             ( $join_on->alias_name, $_->[1]->name )
411             } $fk->column_pairs );
412             }
413             else
414             {
415 0           $sql .=
416             ( join ' AND ',
417 0           map { $join_from->alias_name . '.' . $_->[0]->name .
418             ' = ' .
419             $join_on->alias_name . '.' . $_->[1]->name
420             } $fk->column_pairs );
421             }
422              
423 0           @{ $self->{tables} }{ $join_from, $join_on } = (1, 1);
  0            
424              
425 0 0         if ($where)
426             {
427 0           $sql .= ' AND ';
428              
429             # make a clone
430 0           my $sql_maker = bless { %$self }, ref $self;
431 0           $sql_maker->{sql} = '';
432             # sharing same ref intentionally
433 0           $sql_maker->{bind} = $self->{bind};
434 0           $sql_maker->{tables} = $self->{tables};
435              
436             # lie to Alzabo::Runtime::process_where_clause
437 0           $sql_maker->{last_op} = 'where';
438              
439 0           Alzabo::Runtime::process_where_clause( $sql_maker, $where );
440              
441 0           $sql .= $sql_maker->sql;
442              
443 0           $sql .= ' ';
444              
445 0           $self->{as_id} = $sql_maker->{as_id};
446             }
447              
448 0           return $sql;
449             }
450              
451             sub where
452             {
453 0     0 1   my $self = shift;
454              
455 0           $self->_assert_last_op( qw( from set ) );
456              
457 0           $self->{sql} .= ' WHERE ';
458              
459 0           $self->{last_op} = 'where';
460              
461 0 0         $self->condition(@_) if @_;
462              
463 0           return $self;
464             }
465              
466             sub having
467             {
468 0     0 0   my $self = shift;
469              
470 0           $self->_assert_last_op( qw( group_by ) );
471              
472 0           $self->{sql} .= ' HAVING ';
473              
474 0           $self->{last_op} = 'having';
475              
476 0 0         $self->condition(@_) if @_;
477              
478 0           return $self;
479             }
480              
481             sub and
482             {
483 0     0 1   my $self = shift;
484              
485 0           $self->_assert_last_op( qw( subgroup_end condition ) );
486              
487 0           return $self->_and_or( 'and', @_ );
488             }
489              
490             sub or
491             {
492 0     0 1   my $self = shift;
493              
494 0           $self->_assert_last_op( qw( subgroup_end condition ) );
495              
496 0           return $self->_and_or( 'or', @_ );
497             }
498              
499             sub _and_or
500             {
501 0     0     my $self = shift;
502 0           my $op = shift;
503              
504 0           $self->{sql} .= " \U$op ";
505              
506 0           $self->{last_op} = $op;
507              
508 0 0         $self->condition(@_) if @_;
509              
510 0           return $self;
511             }
512              
513             sub subgroup_start
514             {
515 0     0 0   my $self = shift;
516              
517 0           $self->_assert_last_op( qw( where having and or subgroup_start ) );
518              
519 0           $self->{sql} .= ' (';
520 0   0       $self->{subgroup} ||= 0;
521 0           $self->{subgroup}++;
522              
523 0           $self->{last_op} = 'subgroup_start';
524              
525 0           return $self;
526             }
527              
528             sub subgroup_end
529             {
530 0     0 0   my $self = shift;
531              
532 0           $self->_assert_last_op( qw( condition subgroup_end ) );
533              
534 0 0         Alzabo::Exception::SQL->throw( error => "Can't end a subgroup unless one has been started already" )
535             unless $self->{subgroup};
536              
537 0           $self->{sql} .= ' )';
538 0           $self->{subgroup}--;
539              
540 0 0         $self->{last_op} = $self->{subgroup} ? 'subgroup_end' : 'condition';
541              
542 0           return $self;
543             }
544              
545             sub condition
546             {
547 0     0 0   my $self = shift;
548              
549 0           validate_pos( @_,
550             { type => OBJECT },
551             { type => SCALAR },
552             { type => UNDEF | SCALAR | OBJECT },
553             ( { type => UNDEF | SCALAR | OBJECT, optional => 1 } ) x (@_ - 3) );
554              
555 0           my $lhs = shift;
556 0           my $comp = uc shift;
557 0           my $rhs = shift;
558              
559 0 0         my $in_having = $self->{last_op} eq 'having' ? 1 : 0;
560              
561 0           $self->{last_op} = 'condition';
562              
563 0 0 0       if ( $lhs->can('table') && $lhs->can('name') )
    0          
564             {
565 0 0         unless ( $self->{tables}{ $lhs->table } )
566             {
567 0           my $err = 'Cannot use column (';
568 0           $err .= join '.', $lhs->table->name, $lhs->name;
569 0           $err .= ") in $self->{type} unless its table is included in the ";
570 0 0         $err .= $self->{type} eq 'update' ? 'UPDATE' : 'FROM';
571 0           $err .= ' clause';
572 0           Alzabo::Exception::SQL->throw( error => $err );
573             }
574              
575 0 0         $self->{sql} .=
576             ( $self->{quote_identifiers} ?
577             $self->{driver}->quote_identifier( $lhs->table->alias_name, $lhs->name ) :
578             $lhs->table->alias_name . '.' . $lhs->name );
579             }
580             elsif ( $lhs->isa('Alzabo::SQLMaker::Function') )
581             {
582 0           my $string = $lhs->as_string( $self->{driver}, $self->{quote_identifiers} );
583              
584 0 0 0       if ( exists $self->{functions}{$string} &&
      0        
585             ( ! $in_having || $self->{alias_in_having} ) )
586             {
587 0           $self->{sql} .= $self->{functions}{$string};
588             }
589             else
590             {
591 0           $self->{sql} .= $string;
592             }
593             }
594             else
595             {
596 0           Alzabo::Exception::SQL->throw
597             ( error => "Cannot use " . (ref $lhs) . " object as part of condition" );
598             }
599              
600 0 0         if ( $comp eq 'BETWEEN' )
601             {
602 0 0         Alzabo::Exception::SQL->throw
603             ( error => "The BETWEEN comparison operator requires an additional argument" )
604             unless @_ == 1;
605              
606 0           my $rhs2 = shift;
607              
608 0           Alzabo::Exception::SQL->throw
609             ( error => "The BETWEEN comparison operator cannot accept a subselect" )
610 0 0         if grep { Alzabo::Utils::safe_isa( $_, 'Alzabo::SQLMaker' ) } $rhs, $rhs2;
611              
612 0           $self->{sql} .= ' BETWEEN ';
613 0           $self->{sql} .= $self->_rhs($rhs);
614 0           $self->{sql} .= " AND ";
615 0           $self->{sql} .= $self->_rhs($rhs2);
616              
617 0           return;
618             }
619              
620 0 0 0       if ( $comp eq 'IN' || $comp eq 'NOT IN' )
621             {
622 0           $self->{sql} .= " $comp (";
623              
624 0 0         $self->{sql} .=
625 0           join ', ', map { Alzabo::Utils::safe_isa( $_, 'Alzabo::SQLMaker' )
626             ? '(' . $self->_subselect($_) . ')'
627             : $self->_rhs($_) } $rhs, @_;
628 0           $self->{sql} .= ')';
629              
630 0           return;
631             }
632              
633             Alzabo::Exception::Params->throw
634 0 0         ( error => 'Too many parameters to Alzabo::SQLMaker->condition method' )
635             if @_;
636              
637 0 0 0       if ( ! ref $rhs && defined $rhs )
    0          
    0          
638             {
639 0           $self->{sql} .= " $comp ";
640 0           $self->{sql} .= $self->_rhs($rhs);
641             }
642             elsif ( ! defined $rhs )
643             {
644 0 0 0       if ( $comp eq '=' )
    0          
645             {
646 0           $self->{sql} .= ' IS NULL';
647             }
648             elsif ( $comp eq '!=' || $comp eq '<>' )
649             {
650 0           $self->{sql} .= ' IS NOT NULL';
651             }
652             else
653             {
654 0           Alzabo::Exception::SQL->throw
655             ( error => "Cannot compare a column to a NULL with '$comp'" );
656             }
657             }
658             elsif ( ref $rhs )
659             {
660 0           $self->{sql} .= " $comp ";
661 0 0         if( $rhs->isa('Alzabo::SQLMaker') )
662             {
663 0           $self->{sql} .= '(';
664 0           $self->{sql} .= $self->_subselect($rhs);
665 0           $self->{sql} .= ')';
666             }
667             else
668             {
669 0           $self->{sql} .= $self->_rhs($rhs);
670             }
671             }
672             }
673              
674             sub _rhs
675             {
676 0     0     my $self = shift;
677 0           my $rhs = shift;
678              
679 0 0         if ( Alzabo::Utils::safe_can( $rhs, 'table' ) )
680             {
681 0 0         unless ( $self->{tables}{ $rhs->table } )
682             {
683 0           my $err = 'Cannot use column (';
684 0           $err .= join '.', $rhs->table->name, $rhs->name;
685 0           $err .= ") in $self->{type} unless its table is included in the ";
686 0 0         $err .= $self->{type} eq 'update' ? 'UPDATE' : 'FROM';
687 0           $err .= ' clause';
688 0           Alzabo::Exception::SQL->throw( error => $err );
689             }
690              
691 0 0         return ( $self->{quote_identifiers} ?
692             $self->{driver}->quote_identifier( $rhs->table->alias_name, $rhs->name ) :
693             $rhs->table->alias_name . '.' . $rhs->name );
694             }
695             else
696             {
697 0           return $self->_bind_val($rhs);
698             }
699             }
700              
701             sub _subselect
702             {
703 0     0     my $self = shift;
704 0           my $sql = shift;
705              
706 0           push @{ $self->{bind} }, @{ $sql->bind };
  0            
  0            
707              
708 0           return $sql->sql;
709             }
710              
711             sub order_by
712             {
713 0     0 1   my $self = shift;
714              
715 0           $self->_assert_last_op( qw( select from condition group_by ) );
716              
717 0 0         Alzabo::Exception::SQL->throw
718             ( error => "Cannot use order by in a '$self->{type}' statement" )
719             unless $self->{type} eq 'select';
720              
721             validate_pos( @_, ( { type => SCALAR | OBJECT,
722             callbacks =>
723             { 'column_or_function_or_sort' =>
724 0 0 0 0     sub { Alzabo::Utils::safe_can( $_[0], 'table' ) ||
725             Alzabo::Utils::safe_isa( $_[0], 'Alzabo::SQLMaker::Function' ) ||
726             $_[0] =~ /^(?:ASC|DESC)$/i } } }
727 0           ) x @_ );
728              
729 0           $self->{sql} .= ' ORDER BY ';
730              
731 0           my $x = 0;
732 0           my $last = '';
733 0           foreach my $i (@_)
734             {
735 0 0         if ( Alzabo::Utils::safe_can( $i, 'table' ) )
    0          
736             {
737 0 0         unless ( $self->{tables}{ $i->table } )
738             {
739 0           my $err = 'Cannot use column (';
740 0           $err .= join '.', $i->table->name, $i->name;
741 0           $err .= ") in $self->{type} unless its table is included in the FROM clause";
742 0           Alzabo::Exception::SQL->throw( error => $err );
743             }
744              
745             # no comma needed for first column
746 0 0         $self->{sql} .= ', ', if $x++;
747 0 0         $self->{sql} .=
748             ( $self->{quote_identifiers} ?
749             $self->{driver}->quote_identifier( $i->table->alias_name, $i->alias_name ) :
750             $i->table->alias_name . '.' . $i->alias_name );
751              
752 0           $last = 'column';
753             }
754             elsif ( Alzabo::Utils::safe_isa( $i, 'Alzabo::SQLMaker::Function' ) )
755             {
756 0           my $string = $i->as_string( $self->{driver}, $self->{quote_identifiers} );
757 0 0         if ( exists $self->{functions}{$string} )
758             {
759 0 0         $self->{sql} .= ', ', if $x++;
760 0           $self->{sql} .= $self->{functions}{$string};
761             }
762             else
763             {
764 0 0         $self->{sql} .= ', ', if $x++;
765 0           $self->{sql} .= $string;
766             }
767              
768 0           $last = 'function';
769             }
770             else
771             {
772 0 0         Alzabo::Exception::Params->throw
773             ( error => 'A sort specifier cannot follow another sort specifier in an ORDER BY clause' )
774             if $last eq 'sort';
775              
776 0           $self->{sql} .= " \U$i";
777              
778 0           $last = 'sort';
779             }
780             }
781              
782 0           $self->{last_op} = 'order_by';
783              
784 0           return $self;
785             }
786              
787             sub group_by
788             {
789 0     0 0   my $self = shift;
790              
791 0           $self->_assert_last_op( qw( select from condition ) );
792              
793 0 0         Alzabo::Exception::SQL->throw
794             ( error => "Cannot use group by in a '$self->{type}' statement" )
795             unless $self->{type} eq 'select';
796              
797 0           validate_pos( @_, ( { can => 'table' } ) x @_ );
798              
799 0           foreach my $c (@_)
800             {
801 0 0         unless ( $self->{tables}{ $c->table } )
802             {
803 0           my $err = 'Cannot use column (';
804 0           $err .= join '.', $c->table->name, $c->name;
805 0           $err .= ") in $self->{type} unless its table is included in the FROM clause";
806 0           Alzabo::Exception::SQL->throw( error => $err );
807             }
808             }
809              
810 0           $self->{sql} .= ' GROUP BY ';
811 0 0         $self->{sql} .=
812             ( join ', ',
813 0           map { ( $self->{quote_identifiers} ?
814             $self->{driver}->quote_identifier( $_->table->alias_name, $_->alias_name ) :
815             $_->table->alias_name . '.' . $_->alias_name ) }
816             @_ );
817              
818 0           $self->{last_op} = 'group_by';
819              
820 0           return $self;
821             }
822              
823             sub insert
824             {
825 0     0 1   my $self = shift;
826              
827 0           $self->{sql} .= 'INSERT ';
828              
829 0           $self->{type} = 'insert';
830 0           $self->{last_op} = 'insert';
831              
832 0           return $self;
833             }
834              
835             sub into
836             {
837 0     0 1   my $self = shift;
838              
839 0           $self->_assert_last_op( qw( insert ) );
840              
841 0           validate_pos( @_, { can => 'alias_name' }, ( { can => 'table' } ) x (@_ - 1) );
842              
843 0           my $table = shift;
844 0           $self->{tables} = { $table => 1 };
845              
846 0           foreach my $c (@_)
847             {
848 0 0         unless ( $c->table eq $table )
849             {
850 0           my $err = 'Cannot into column (';
851 0           $err .= join '.', $c->table->name, $c->name;
852 0           $err .= ') because its table was not the one specified in the INTO clause';
853 0           Alzabo::Exception::SQL->throw( error => $err );
854             }
855             }
856              
857 0 0         $self->{columns} = [ @_ ? @_ : $table->columns ];
858              
859 0           $self->{sql} .= 'INTO ';
860              
861 0 0         $self->{sql} .= ( $self->{quote_identifiers} ?
862             $self->{driver}->quote_identifier( $table->name ) :
863             $table->name );
864              
865 0           $self->{sql} .= ' (';
866              
867 0 0         $self->{sql} .=
868             ( join ', ',
869 0           map { ( $self->{quote_identifiers} ?
870             $self->{driver}->quote_identifier( $_->name ) :
871             $_->name ) }
872 0           @{ $self->{columns} } );
873              
874 0           $self->{sql} .= ') ';
875              
876 0           $self->{last_op} = 'into';
877              
878 0           return $self;
879             }
880              
881             sub values
882             {
883 0     0 1   my $self = shift;
884              
885 0           $self->_assert_last_op( qw( into ) );
886              
887 0           validate_pos( @_, ( { type => UNDEF | SCALAR | OBJECT } ) x @_ );
888              
889 0 0 0       if ( ref $_[0] && $_[0]->isa('Alzabo::SQLMaker') )
890             {
891 0           $self->{sql} = $_[0]->sql;
892 0           push @{ $self->{bind} }, $_[0]->bind;
  0            
893             }
894             else
895             {
896 0           my @vals = @_;
897              
898 0 0 0       Alzabo::Exception::Params->throw
899             ( error => "'values' method expects key/value pairs of column objects and values'" )
900             if !@vals || @vals % 2;
901              
902 0 0 0       my %vals = map { ref $_ && $_->can('table') ? $_->name : $_ } @vals;
  0            
903 0           foreach my $c ( @vals[ map { $_ * 2 } 0 .. int($#vals/2) ] )
  0            
904             {
905 0           Alzabo::Exception::SQL->throw
906             ( error => $c->name . " column was not specified in the into method call" )
907 0 0         unless grep { $c eq $_ } @{ $self->{columns} };
  0            
908             }
909              
910 0           foreach my $c ( @{ $self->{columns } } )
  0            
911             {
912 0 0         Alzabo::Exception::SQL->throw
913             ( error => $c->name . " was specified in the into method call but no value was provided" )
914             unless exists $vals{ $c->name };
915             }
916              
917 0           $self->{sql} .= 'VALUES (';
918 0           $self->{sql} .=
919 0           join ', ', ( map { $self->_bind_val_for_insert( $_, $vals{ $_->name } ) }
920 0           @{ $self->{columns} }
921             );
922 0           $self->{sql} .= ')';
923             }
924              
925 0 0 0       if ( @{ $self->{placeholders} } && @{ $self->{bind} } )
  0            
  0            
926             {
927 0           Alzabo::Exception::SQL->throw
928             ( error => "Cannot mix actual bound values and placeholders in call to values()" );
929             }
930              
931 0           $self->{last_op} = 'values';
932              
933 0           return $self;
934             }
935              
936 11     11   111 use constant UPDATE_SPEC => { can => 'alias_name' };
  11         24  
  11         7959  
937              
938             sub update
939             {
940 0     0 1   my $self = shift;
941              
942 0           validate_pos( @_, UPDATE_SPEC );
943              
944 0           my $table = shift;
945              
946 0           $self->{sql} = 'UPDATE ';
947              
948 0 0         $self->{sql} .= ( $self->{quote_identifiers} ?
949             $self->{driver}->quote_identifier( $table->name ) :
950             $table->name );
951              
952 0           $self->{tables} = { $table => 1 };
953              
954 0           $self->{type} = 'update';
955 0           $self->{last_op} = 'update';
956              
957 0           return $self;
958             }
959              
960             sub set
961             {
962 0     0 1   my $self = shift;
963 0           my @vals = @_;
964              
965 0           $self->_assert_last_op('update');
966              
967 0 0 0       Alzabo::Exception::Params->throw
968             ( error => "'set' method expects key/value pairs of column objects and values'" )
969             if !@vals || @vals % 2;
970              
971 0           validate_pos( @_, ( { can => 'table' },
972             { type => UNDEF | SCALAR | OBJECT } ) x (@vals / 2) );
973              
974 0           $self->{sql} .= ' SET ';
975              
976 0           my @set;
977 0           my $table = ( keys %{ $self->{tables} } )[0];
  0            
978 0           while ( my ($col, $val) = splice @vals, 0, 2 )
979             {
980 0 0         unless ( $table eq $col->table )
981             {
982 0           my $err = 'Cannot set column (';
983 0           $err .= join '.', $col->table->name, $col->name;
984 0           $err .= ') unless its table is included in the UPDATE clause';
985 0           Alzabo::Exception::SQL->throw( error => $err );
986             }
987              
988 0 0         push @set,
989             ( $self->{quote_identifiers} ?
990             $self->{driver}->quote_identifier( $col->name ) :
991             $col->name ) .
992             ' = ' . $self->_bind_val($val);
993             }
994 0           $self->{sql} .= join ', ', @set;
995              
996 0           $self->{last_op} = 'set';
997              
998 0           return $self;
999             }
1000              
1001             sub delete
1002             {
1003 0     0 1   my $self = shift;
1004              
1005 0           $self->{sql} .= 'DELETE ';
1006              
1007 0           $self->{type} = 'delete';
1008 0           $self->{last_op} = 'delete';
1009              
1010 0           return $self;
1011             }
1012              
1013             sub _assert_last_op
1014             {
1015 0     0     my $self = shift;
1016              
1017 0 0         unless ( grep { $self->{last_op} eq $_ } @_ )
  0            
1018             {
1019 0           my $op = (caller(1))[3];
1020 0           $op =~ s/.*::(.*?)$/$1/;
1021 0           Alzabo::Exception::SQL->throw( error => "Cannot follow $self->{last_op} with $op" );
1022             }
1023             }
1024              
1025 11         2400 use constant _BIND_VAL_FOR_INSERT_SPEC => ( { isa => 'Alzabo::Runtime::Column' },
1026             { type => UNDEF | SCALAR | OBJECT }
1027 11     11   75 );
  11         24  
1028              
1029              
1030             sub _bind_val_for_insert
1031             {
1032 0     0     my $self = shift;
1033              
1034 0           my ( $col, $val ) =
1035             validate_pos( @_, _BIND_VAL_FOR_INSERT_SPEC );
1036              
1037 0 0 0       if ( defined $val && $val eq $placeholder )
1038             {
1039 0           push @{ $self->{placeholders} }, $col->name;
  0            
1040 0           return '?';
1041             }
1042             else
1043             {
1044 0           return $self->_bind_val($val);
1045             }
1046             }
1047              
1048 11     11   78 use constant _BIND_VAL_SPEC => { type => UNDEF | SCALAR | OBJECT };
  11         45  
  11         7140  
1049              
1050             sub _bind_val
1051             {
1052 0     0     my $self = shift;
1053              
1054 0           validate_pos( @_, _BIND_VAL_SPEC );
1055              
1056 0 0         return $_[0]->as_string( $self->{driver}, $self->{quote_identifiers} )
1057             if Alzabo::Utils::safe_isa( $_[0], 'Alzabo::SQLMaker::Function' );
1058              
1059 0           push @{ $self->{bind} }, $_[0];
  0            
1060 0           return '?';
1061             }
1062              
1063             sub sql
1064             {
1065 0     0 1   my $self = shift;
1066              
1067 0 0         Alzabo::Exception::SQL->throw( error => "SQL contains unbalanced parentheses subgrouping: $self->{sql}" )
1068             if $self->{subgroup};
1069              
1070 0           return $self->{sql};
1071             }
1072              
1073             sub bind
1074             {
1075 0     0 1   my $self = shift;
1076 0           return $self->{bind};
1077             }
1078              
1079             sub placeholders
1080             {
1081 0     0 0   my $self = shift;
1082              
1083 0           my $x = 0;
1084              
1085 0           return map { $_ => $x++ } @{ $self->{placeholders} };
  0            
  0            
1086             }
1087              
1088             sub limit
1089             {
1090 0     0 1   shift()->_virtual;
1091             }
1092              
1093             sub get_limit
1094             {
1095 0     0 1   shift()->_virtual;
1096             }
1097              
1098             sub sqlmaker_id
1099             {
1100 0     0 1   shift()->_virtual;
1101             }
1102              
1103 0     0 0   sub distinct_requires_order_by_in_select { 0 }
1104              
1105             sub _virtual
1106             {
1107 0     0     my $self = shift;
1108              
1109 0           my $sub = (caller(1))[3];
1110 0           $sub =~ s/.*::(.*?)$/$1/;
1111 0           Alzabo::Exception::VirtualMethod->throw( error =>
1112             "$sub is a virtual method and must be subclassed in " . ref $self );
1113             }
1114              
1115             sub debug
1116             {
1117 0     0 0   my $self = shift;
1118 0           my $fh = shift;
1119              
1120 0           print $fh '-' x 75 . "\n";
1121 0           print $fh "SQL\n - " . $self->sql . "\n";
1122 0           print $fh "Bound values\n";
1123              
1124 0           foreach my $b ( @{ $self->bind } )
  0            
1125             {
1126 0           my $out = $b;
1127              
1128 0 0         if ( defined $out )
1129             {
1130 0 0         if ( length $out > 75 )
1131             {
1132 0           $out = substr( $out, 0, 71 ) . ' ...';
1133             }
1134             }
1135             else
1136             {
1137 0           $out = 'NULL';
1138             }
1139              
1140 0           print $fh " - [$out]\n";
1141             }
1142             }
1143              
1144             package Alzabo::SQLMaker::Function;
1145              
1146 11     11   114 use Params::Validate qw( :all );
  11         31  
  11         9401  
1147             Params::Validate::validation_options( on_fail => sub { Alzabo::Exception::Params->throw( error => join '', @_ ) } );
1148              
1149             sub new
1150             {
1151 0     0     my $class = shift;
1152 0           my %p = @_;
1153              
1154 0 0         $p{args} = [] unless defined $p{args};
1155 0   0       $p{quote} ||= [];
1156              
1157 0           return bless \%p, $class;
1158             }
1159              
1160 0     0     sub allows_alias { shift->{allows_alias} }
1161              
1162             sub as_string
1163             {
1164 0     0     my $self = shift;
1165 0           my $driver = shift;
1166 0           my $quote = shift;
1167              
1168 0           my @args;
1169 0           foreach ( 0..$#{ $self->{args} } )
  0            
1170             {
1171 0 0         if ( Alzabo::Utils::safe_can( $self->{args}[$_], 'table' ) )
    0          
1172             {
1173 0 0         push @args,
1174             ( $quote ?
1175             $driver->quote_identifier( $self->{args}[$_]->table->alias_name,
1176             $self->{args}[$_]->name ) :
1177             $self->{args}[$_]->table->alias_name . '.' .
1178             $self->{args}[$_]->name );
1179 0           next;
1180             }
1181             elsif ( Alzabo::Utils::safe_isa( $self->{args}[$_], 'Alzabo::SQLMaker::Function' ) )
1182             {
1183 0           push @args, $self->{args}[$_]->as_string( $driver, $quote );
1184 0           next;
1185             }
1186              
1187             # if there are more args than specified in the quote param
1188             # then this function must allow an unlimited number of
1189             # arguments, in which case the last value in the quote param
1190             # is the value that should be used for all of the extra
1191             # arguments.
1192 0 0         my $i = $_ > $#{ $self->{quote} } ? -1 : $_;
  0            
1193 0 0         push @args,
1194             $self->{quote}[$i] ? $driver->quote( $self->{args}[$_] ) : $self->{args}[$_];
1195             }
1196              
1197 0           my $sql = $self->{function};
1198 0 0         $sql =~ s/_/ /g if $self->{has_spaces};
1199              
1200 0 0         return $sql if $self->{is_modifier};
1201              
1202 0 0         $sql .= '('
1203             unless $self->{no_parens};
1204              
1205 0 0         if ( $self->{format} )
1206             {
1207 0           $sql .= sprintf( $self->{format}, @args );
1208             }
1209             else
1210             {
1211 0           $sql .= join ', ', @args;
1212             }
1213              
1214 0 0         $sql .= ')'
1215             unless $self->{no_parens};
1216              
1217 0           return $sql;
1218             }
1219              
1220             __END__