File Coverage

blib/lib/SQL/QueryBuilder/OO.pm
Criterion Covered Total %
statement 269 857 31.3
branch 0 240 0.0
condition 0 100 0.0
subroutine 90 207 43.4
pod n/a
total 359 1404 25.5


line stmt bran cond sub pod time code
1             package SQL::QueryBuilder::OO;
2              
3 1     1   9336 use 5.010;
  1         4  
4 1     1   5 use strict;
  1         2  
  1         25  
5 1     1   5 use warnings;
  1         6  
  1         40  
6 1     1   4 use vars qw($VERSION);
  1         3  
  1         56  
7              
8             $VERSION = '0.2.3';
9             ##------------------------------------------------------------------------------
10             package sqlQuery;
11              
12 1     1   4 use strict;
  1         2  
  1         16  
13 1     1   4 use warnings;
  1         2  
  1         28  
14 1     1   1425 use overload '""' => '_getInterpolatedQuery';
  1         1081  
  1         6  
15              
16 1     1   1538 use Data::Dumper; # vital
  1         10112  
  1         129  
17 1     1   8 use Carp qw(croak cluck);
  1         3  
  1         69  
18 1     1   6 use Scalar::Util qw(blessed looks_like_number);
  1         2  
  1         132  
19 1     1   967 use Params::Validate qw(:all);
  1         9085  
  1         3064  
20              
21             $sqlQuery::DBI = undef;
22             %sqlQuery::PARAMS = ();
23             $sqlQuery::PARAMETER_PLACEHOLDER = '?';
24              
25             sub setup
26             {
27 0     0     my %params = validate @_, {
28             -dbh => {isa => 'DBI::db', default => undef},
29             -connect => {type => CODEREF, default => undef}
30             };
31              
32 0 0 0       if (defined $params{'-dbh'} && defined $params{'-connect'})
33             {
34 0           croak('Make up your mind: either use "-dbh" to pass a handle or "-connect" for ad-hoc connecting');
35             }
36              
37 0           %sqlQuery::PARAMS = %params;
38              
39 0           return 1
40             }
41              
42             sub dbh
43             {
44 0 0   0     unless (defined $sqlQuery::DBI) {
45 0 0         if (defined $sqlQuery::PARAMS{'-dbh'}) {
    0          
46 0           $sqlQuery::DBI = $sqlQuery::PARAMS{'-dbh'};
47             } elsif (defined $sqlQuery::PARAMS{'-connect'}) {
48 0           $sqlQuery::DBI = eval {$sqlQuery::PARAMS{'-connect'}->()};
  0            
49 0 0         croak 'Setup failed; ad-hoc connector died: '.$@ if $@;
50             } else {
51 0           croak 'sqlQuery is not setup, yet.';
52             }
53             }
54              
55 0           return $sqlQuery::DBI
56             }
57              
58             sub q
59             {
60 0     0     local $Carp::CarpLevel = $Carp::CarpLevel + 2; ## no critic
61 0           return __PACKAGE__->new(@_);
62             }
63              
64             sub exec
65             {
66 0     0     my $sql = shift;
67 0           my $q = __PACKAGE__->new($sql);
68 0           my $rows = $q->execute(@_);
69 0 0 0       if (blessed($rows) && $rows->isa('sqlQueryResult')) {
70 0           cluck('Discarded query with results');
71 0           $rows = undef;
72             }
73              
74 0           undef $q;
75 0           return $rows
76             }
77              
78             sub foundRows
79             {
80 0     0     my $res = __PACKAGE__->new(q(SELECT FOUND_ROWS()))->execute;
81 0           my $rows = $res->fetchColumn(0);
82 0           $res->freeResource();
83              
84 0           return $rows
85             }
86              
87             sub getLastInsertId
88             {
89 0     0     my $res = __PACKAGE__->new(q(SELECT LAST_INSERT_ID()))->execute;
90 0           my $id = $res->fetchColumn(0);
91 0           $res->freeResource();
92              
93 0           return $id
94             }
95              
96             sub new
97             {
98 0 0   0     my $class = ref $_[0] ? ref shift : shift;
99 0           my $sql = shift;
100 0           my $self = {-sql => undef, -params => undef, -named => 0};
101              
102 0 0         unless (blessed($sql)) {
103 0 0 0       croak 'Not a scalar argument; query must either be a string or an instance of "sqlSelectAssemble"'
      0        
104             if !defined $sql || ref $sql || looks_like_number $sql;
105             } else {
106 0 0         croak sprintf 'Parameter is not an instance of "sqlSelectAssemble" (got "%s")', ref $sql
107             unless $sql->isa('sqlSelectAssemble');
108 0           $self->{'-params'} = undef
109             }
110              
111 0           $self->{'-sql'} = $sql;
112 0           return bless $self, $class
113             }
114              
115             sub debugQuery
116             {
117 0     0     my $self = shift;
118 0           my $sql = "$self->{-sql}";
119              
120 0           $sql =~ s/(?:\r?\n)+$//;
121 0           print "$sql\n";
122              
123 0 0         if (@_) {
    0          
124 0           $self->_populateParameters(@_);
125             } elsif (blessed $self->{'-sql'}) {
126 0           $self->_populateParameters($self->{'-sql'}->gatherBoundArgs());
127             }
128              
129 0 0         if (defined $self->{'-params'}) {
130 0           printf "%s\n%s\n%s\n", ('-'x80), Dumper($self->{'-params'}), ('-'x80);
131 0           $self->_interpolateQuery();
132 0           printf "%s\n", $self->{'-interpolated-query'};
133             }
134              
135             return
136 0           }
137              
138             sub execute
139             {
140 0     0     my $self = shift;
141              
142 0 0         if (@_) {
    0          
143 0           $self->_populateParameters(@_);
144             } elsif (blessed $self->{'-sql'}) {
145 0           $self->_populateParameters($self->{'-sql'}->gatherBoundArgs());
146             }
147              
148 0           $self->_interpolateQuery();
149              
150 0           my $res = eval {$self->_query($self->{'-interpolated-query'})};
  0            
151 0           $self->{'-params'} = undef;
152 0 0         croak $@ if $@;
153              
154 0           return $res
155             }
156              
157             sub setParameters
158             {
159 0     0     my $self = shift;
160 0           $self->_populateParameters(@_);
161 0           return $self
162             }
163              
164             sub _getInterpolatedQuery
165             {
166 0     0     my $self = shift;
167 0           $self->_interpolateQuery();
168 0           return $self->{'-interpolated-query'}
169             }
170              
171             sub _populateParameters
172             {
173 0     0     my $self = shift;
174              
175 0 0         if (defined $self->{'-params'}) {
176 0           local $Carp::CarpLevel = $Carp::CarpLevel + 2; ## no critic
177 0           croak 'Query parameters are already populated'
178             }
179              
180 0 0 0       if (1 == scalar @_ && 'HASH' eq ref $_[0]) {
181 0           $self->{'-named'} = 1;
182 0           $self->{'-params'} = shift;
183 0           foreach my $p (keys %{$self->{'-params'}}) {
  0            
184 0           $self->{'-params'}->{$p} = _convertArgument($self->{'-params'}->{$p});
185             croak "Argument '$p' could not be converted"
186 0 0         unless defined $self->{'-params'}->{$p};
187             }
188             } else {
189 0           foreach (@_) {
190 0 0         croak 'Mixed named and positional parameters are unsupported'
191             if 'HASH' eq ref $_;
192             }
193              
194 0           $self->{'-named'} = 0;
195 0           $self->{'-params'} = [@_];
196              
197 0           foreach my $index (0..$#_) {
198 0           $self->{'-params'}->[$index] = _convertArgument($self->{'-params'}->[$index]);
199             croak "Argument at index '$index' could not be converted"
200 0 0         unless defined $self->{'-params'}->[$index];
201             }
202             }
203              
204             return
205 0           }
206              
207             sub _interpolateQuery
208             {
209 0     0     my $self = shift;
210              
211 0 0         if ($self->{'-named'}) {
212 0           $self->_interpolateByName();
213             } else {
214 0           $self->_interpolateByIndex();
215             }
216              
217 0           return $self->_checkLeftoverParameters();
218             }
219              
220             sub _interpolateByIndex
221             {
222 0     0     my $self = shift;
223 0           my $sql = "$self->{-sql}";
224 0           my $pos = 0;
225              
226 0           while (1) {
227 0 0 0       last if $pos >= length($sql) || 0 > ($pos = index $sql, $sqlQuery::PARAMETER_PLACEHOLDER, $pos);
228              
229 0           my $param = eval{$self->_fetchParameter()};
  0            
230 0 0         croak "$@: interpolated so far: $sql" if $@;
231 0           my $value = "$param";
232              
233 0 0         $sql =
234             (0 < $pos ? substr($sql, 0, $pos) : '') .
235             $value .
236             (substr $sql, $pos + 1);
237 0           $pos += length $value;
238             }
239              
240 0           $self->{'-interpolated-query'} = $sql;
241              
242             return
243 0           }
244              
245             sub _interpolateByName
246             {
247 0     0     my $self = shift;
248 0           my $sql = "$self->{-sql}";
249 0           my $pos = 0;
250              
251 0           while(1) {
252 0 0 0       last if $pos >= length($sql) || 0 > ($pos = index $sql, q(:), $pos);
253              
254 0           my ($name) = (substr $sql, $pos) =~ m/^:([[:lower:][:upper:]_\d-]+)/;
255 0           my $param = eval{$self->_fetchParameter($name)};
  0            
256 0 0         croak "$@: interpolated so far: $sql" if $@;
257 0           my $value = "$param";
258              
259 0 0         $sql = (0 < $pos ? substr($sql, 0, $pos) : '') .
260             $value .
261             (substr $sql, $pos + 1 + length $name);
262 0           $pos += length $value;
263             }
264              
265 0           $self->{'-interpolated-query'} = $sql;
266              
267             return
268 0           }
269              
270             sub _fetchParameter
271             {
272 0     0     my $self = shift;
273 0           my $name = shift;
274              
275 0 0         if (defined $name) {
276 0 0         if (!exists($self->{'-params'}->{$name})) {
277 0           croak sprintf 'No such query parameter "%s"', $name;
278             }
279 0           return $self->{'-params'}->{$name};
280             } else {
281 0 0 0       unless (ref $self->{'-params'} && @{$self->{'-params'}}) {
  0            
282 0           croak 'Too few query parameters provided';
283             }
284             }
285              
286 0           return shift @{$self->{'-params'}};
  0            
287             }
288              
289             sub _checkLeftoverParameters
290             {
291 0     0     my $self = shift;
292              
293 0 0 0       if ('ARRAY' eq ref $self->{'-params'} && @{$self->{'-params'}}) {
  0            
294 0           croak 'Too many query parameters provided';
295             }
296              
297             return
298 0           }
299              
300             sub _query
301             {
302 0     0     my $self = shift;
303 0           my $sql = shift;
304 0           my $dbh = sqlQuery::dbh();
305 0           my $error;
306              
307             EXECUTE: {
308 0 0         if ($sql !~ m/^select/i) {
  0            
309 0           local $dbh->{RaiseError} = 1;
310 0           local $dbh->{PrintError} = 0;
311 0           my $rows;
312 0 0         eval{$dbh->do($sql); 1} or do {
  0            
  0            
313 0           $error = $@;
314 0           last EXECUTE;
315             };
316 0           return $rows;
317             }
318              
319 0           $self->{'-sth'} = $dbh->prepare($sql);
320              
321 0           local $self->{'-sth'}->{RaiseError} = 1;
322 0           local $self->{'-sth'}->{PrintError} = 0;
323 0 0         eval {$self->{'-sth'}->execute; 1} or do {
  0            
  0            
324 0           $error = $@;
325 0           last EXECUTE;
326             };
327 0           return sqlQueryResult->new($self, $self->{'-sth'});
328             }
329              
330 0           my $file = __FILE__;
331              
332 0           $self->{'-sth'} = undef;
333              
334 0           $error =~ s/\s+at $file line \d+[.]\r?\n//;
335 0           $error =~ s/\s*at line \d$//;
336 0           $sql =~ s/(?:\r?\n)+$//;
337 0           croak "$error\n\n<
338             }
339              
340             sub quoteTable
341             {
342 0     0     my $table = shift;
343              
344 0 0         if (ref $table)
345             {
346 0           my ($k,$v);
347 0           ($k) = keys %$table;
348 0           ($v) = values %$table;
349 0           return sprintf '%s AS %s', sqlQuery::quoteTable($k), sqlQuery::quoteTable($v);
350             }
351              
352 0 0         return '*'
353             if '*' eq $table;
354 0           $table = join '.', map {"`$_`"} split /[.]/, $table;
  0            
355 0           $table =~ s/`+/`/g;
356 0           return $table
357             }
358              
359             sub quoteWhenTable
360             {
361 0     0     my $table = shift;
362              
363 0 0 0       return sqlQuery::quoteTable($table)
364             if ref $table || ".$table" =~ m/^(?:[.][[:lower:]_][[:lower:]\d_]*){1,2}$/i;
365              
366 0 0         if ($table =~ m/^([[:lower:]_][[:lower:]\d_]*)[.][*]$/i) {
367 0           return sqlQuery::quoteTable($1).'.*';
368             } else {
369 0           return $table;
370             }
371             }
372              
373             sub convertArgument
374             {
375 0     0     my $arg = shift;
376 0           my $value = _convertArgument($arg);
377              
378 0 0         unless (defined $value) {
379 0           local $Carp::CarpLevel = $Carp::CarpLevel + 1; ## no critic
380 0           croak 'Argument to "sqlCondition::bind()" cannot be converted; consider using an implicit "sqlValue" instance instead'
381             }
382              
383 0           return $value
384             }
385              
386             sub _convertArgument
387             {
388 0     0     my $arg = shift;
389              
390 0 0 0       unless(ref $arg) {
    0          
    0          
391 0 0         return sqlValueNull->new
392             unless defined $arg;
393 0 0         return sqlValueInt->new($arg)
394             if $arg =~ m/^-?\d+$/;
395 0 0         return sqlValueFloat->new($arg)
396             if $arg =~ m/^-?\d+[.]\d+$/;
397 0           return sqlValueString->new($arg);
398             } elsif ('ARRAY' eq ref $arg) {
399 0           return sqlValueList->new($arg);
400             } elsif (blessed $arg && $arg->isa('sqlParameter')) {
401 0           return $arg;
402             }
403              
404             return
405 0           }
406             ##------------------------------------------------------------------------------
407             package sqlQueryResult;
408              
409 1     1   7 use strict;
  1         2  
  1         19  
410 1     1   4 use warnings;
  1         1  
  1         32  
411 1     1   12 use Carp qw(croak);
  1         2  
  1         47  
412 1     1   5 use Scalar::Util qw(looks_like_number);
  1         1  
  1         422  
413              
414             sub new
415             {
416 0 0   0     my $class = ref $_[0] ? ref shift : shift;
417 0           my $query = shift;
418 0           my $result = shift;
419              
420 0           return bless {-query => $query, -result => $result}, $class;
421             }
422              
423 0     0     sub fetchAssoc {goto &fetchRow}
424             sub fetchRow
425             {
426 0     0     my $self = shift;
427 0           return $self->{'-result'}->fetchrow_hashref
428             }
429              
430             sub fetchArray
431             {
432 0     0     my $self = shift;
433 0           return $self->{'-result'}->fetchrow_array;
434             }
435              
436             sub fetchColumn
437             {
438 0     0     my $self = shift;
439 0   0       my $column = shift || '0';
440              
441 0 0         if (looks_like_number $column) {
442 0           my @row = $self->{'-result'}->fetchrow_array;
443 0 0         croak "No such query result offset $column"
444             if $column > $#row;
445 0           return $row[$column];
446             } else {
447 0           my $row = $self->fetchRow();
448             croak "No such query result column $column"
449 0 0         unless exists($row->{$column});
450 0           return $row->{$column};
451             }
452             }
453              
454             sub fetchAll
455             {
456 0     0     my $self = shift;
457 0           my ($row,@rows);
458              
459 0           push @rows, $row
460             while defined($row = $self->fetchAssoc());
461             return @rows
462 0           }
463              
464 0     0     sub numRows {goto &getNumRows}
465             sub getNumRows
466             {
467 0     0     return shift->{'-result'}->rows;
468             }
469              
470             sub freeResource
471             {
472 0     0     my $self = shift;
473              
474             croak 'Statement seems unexecuted'
475 0 0         unless defined $self->{'-result'};
476 0           $self->{'-result'}->finish();
477 0           undef $self->{'-result'};
478              
479 0           return $self;
480             }
481             ##------------------------------------------------------------------------------
482             package sqlQueryBase;
483              
484 1     1   8 use strict;
  1         1  
  1         23  
485 1     1   5 use warnings;
  1         2  
  1         172  
486             ##------------------------------------------------------------------------------
487             sub select
488             {
489 0     0     my @fields;
490             my @params;
491              
492 0 0 0       if (@_ && 'ARRAY' eq ref $_[-1]) {
493 0           @params = @{pop()}; ## no critic
  0            
494             }
495              
496 0 0         unless (@_) {
497 0           @fields = '*';
498             } else {
499             @fields = (
500 0           (split /,/, (join ',', grep {!ref} @_)),
501 0           grep {ref} @_
  0            
502             );
503             }
504              
505 0           return sqlSelectFrom->new(
506             fields => [@fields],
507             params => [@params]
508             );
509             }
510             ##------------------------------------------------------------------------------
511             package sqlParameter;
512              
513 1     1   5 use strict;
  1         2  
  1         17  
514 1     1   4 use warnings;
  1         5  
  1         23  
515 1     1   4 use overload '""' => 'getSafeQuotedValue';
  1         7  
  1         8  
516 1     1   61 use Carp qw(croak);
  1         2  
  1         105  
517              
518             sub new
519             {
520 0 0   0     my $class = ref $_[0] ? ref shift : shift;
521 0           return bless {-value => shift}, $class;
522             }
523              
524             sub getSafeQuotedValue
525             {
526 0     0     croak 'sqlParameter::getSafeQuotedValue() is abstract; implement it in '.(ref $_[0])
527             }
528             ##------------------------------------------------------------------------------
529             package sqlValueNull;
530              
531 1     1   4 use strict;
  1         2  
  1         23  
532 1     1   5 use warnings;
  1         1  
  1         28  
533 1     1   4 use base 'sqlParameter';
  1         2  
  1         740  
534              
535 0     0     sub getSafeQuotedValue {return 'NULL'}
536             ##------------------------------------------------------------------------------
537             package sqlValueLiteral;
538              
539 1     1   5 use strict;
  1         1  
  1         21  
540 1     1   4 use warnings;
  1         2  
  1         27  
541 1     1   4 use base 'sqlParameter';
  1         1  
  1         365  
542              
543 0     0     sub getSafeQuotedValue {return shift->{-value}}
544             ##------------------------------------------------------------------------------
545             package sqlValueString;
546              
547 1     1   4 use strict;
  1         2  
  1         22  
548 1     1   4 use warnings;
  1         2  
  1         21  
549 1     1   4 use base 'sqlParameter';
  1         2  
  1         431  
550              
551             sub getSafeQuotedValue {
552 0     0     my $self = shift;
553 0           return sqlQuery::dbh()->quote($self->{-value});
554             }
555             ##------------------------------------------------------------------------------
556             package sqlValueInt;
557              
558 1     1   5 use strict;
  1         1  
  1         21  
559 1     1   3 use warnings;
  1         2  
  1         23  
560 1     1   4 use base 'sqlParameter';
  1         1  
  1         384  
561              
562             sub getSafeQuotedValue {
563             return int(shift->{-value})
564 0     0     }
565             ##------------------------------------------------------------------------------
566             package sqlValueFloat;
567              
568 1     1   5 use strict;
  1         1  
  1         21  
569 1     1   4 use warnings;
  1         17  
  1         22  
570 1     1   5 use base 'sqlParameter';
  1         1  
  1         449  
571              
572             sub new {
573 0     0     my $self = shift->SUPER::new(@_);
574 0   0       $self->{-precision} = $_[1] || 8;
575 0           return $self
576             }
577              
578             sub getSafeQuotedValue {
579 0     0     my $self = shift;
580             return sprintf '%.'.$self->{-precision}.'f', $self->{-value}
581 0           }
582             ##------------------------------------------------------------------------------
583             package sqlValueList;
584              
585 1     1   6 use strict;
  1         1  
  1         28  
586 1     1   4 use warnings;
  1         1  
  1         28  
587 1     1   4 use base 'sqlParameter';
  1         2  
  1         338  
588 1     1   5 use Carp qw(croak);
  1         1  
  1         155  
589              
590             sub new {
591 0     0     my $self = shift->SUPER::new(@_);
592              
593 0 0         unless (@{$self->{-value}}) {
  0            
594 0           local $Carp::CarpLevel = $Carp::CarpLevel + 2; ## no critic
595 0           croak 'Empty lists can break SQL syntax.';
596             }
597              
598 0           return $self
599             }
600              
601             sub getSafeQuotedValue {
602 0     0     return join ',', map {"$_"} @{shift->{-value}};
  0            
  0            
603             }
604             ##------------------------------------------------------------------------------
605             package sqlValueDateTimeBase;
606              
607 1     1   4 use strict;
  1         2  
  1         20  
608 1     1   4 use warnings;
  1         1  
  1         22  
609 1     1   4 use base 'sqlParameter';
  1         1  
  1         366  
610              
611 1     1   5 use Carp;
  1         1  
  1         60  
612 1     1   719 use Date::Parse;
  1         11556  
  1         144  
613 1     1   6 use Scalar::Util qw(looks_like_number);
  1         2  
  1         206  
614              
615             sub new {
616 0     0     my $self = shift->SUPER::new(@_);
617              
618 0 0         unless (defined $self->{-value}) {
    0          
619 0           $self->{-value} = time;
620             } elsif (looks_like_number $self->{-value}) {
621              
622             } else {
623 0           $self->{-value} = str2time($self->{-value});
624             }
625              
626 0           return $self
627             }
628              
629             sub getSafeQuotedValue {
630 0     0     return sqlQuery::dbh()->quote(shift->format());
631             }
632              
633             sub format {
634 0     0     croak __PACKAGE__.'::format() is "abstract"';
635             }
636             ##------------------------------------------------------------------------------
637             package sqlValueDate;
638              
639 1     1   5 use strict;
  1         2  
  1         22  
640 1     1   5 use warnings;
  1         2  
  1         37  
641 1     1   5 use base 'sqlValueDateTimeBase';
  1         2  
  1         800  
642 1     1   1046 use POSIX qw(strftime);
  1         7057  
  1         12  
643              
644             sub format {
645 0     0     return strftime '%Y-%m-%d', localtime shift->{-value};
646             }
647             ##------------------------------------------------------------------------------
648             package sqlValueDateTime;
649              
650 1     1   1267 use strict;
  1         2  
  1         19  
651 1     1   5 use warnings;
  1         2  
  1         30  
652 1     1   4 use base 'sqlValueDateTimeBase';
  1         2  
  1         390  
653 1     1   4 use POSIX qw(strftime);
  1         2  
  1         3  
654              
655             sub format {
656 0     0     return strftime '%Y-%m-%d %H:%M:%S', localtime shift->{-value};
657             }
658             ##------------------------------------------------------------------------------
659             package sqlSelectAssemble;
660              
661 1     1   78 use strict;
  1         1  
  1         16  
662 1     1   4 use warnings;
  1         2  
  1         25  
663 1     1   5 use Carp qw/confess/;
  1         2  
  1         42  
664 1     1   4 use overload '""' => 'assemble';
  1         2  
  1         7  
665              
666             sub new
667             {
668 0     0     my $class = shift;
669 0           my ($prev,$prevClass,%args) = @_;
670 0           my $self = bless {boundArgs => undef, prev => $prev, %args}, $class;
671              
672 0 0         if ($prevClass) {
673             confess sprintf 'Invalid predecessor. Got "%s". Wanted "%s"', ref $self->{prev}, $prevClass
674 0 0 0       unless ref $self->{prev} && $self->{prev}->isa($prevClass);
675             }
676              
677 0           return $self
678             }
679              
680             sub addBoundArgs
681             {
682 0     0     my $self = shift;
683 0           push @{$self->{boundArgs}}, @_;
  0            
684 0           return $self
685             }
686              
687             sub gatherBoundArgs
688             {
689 0     0     my $self = shift;
690 0           my (@args);
691              
692 0           push @args, @{$self->{boundArgs}}
693 0 0         if $self->{boundArgs};
694 0           push @args, $self->gatherConditionArgs();
695              
696 0 0         if ($self->{prev}) {
697 0           push @args, $self->{prev}->gatherBoundArgs();
698             }
699              
700             return @args
701 0           }
702              
703       0     sub gatherConditionArgs {}
704              
705             sub assemble
706             {
707 0     0     my $self = shift;
708 0           my $assembled = $self->_assemble();
709              
710             $assembled = $self->{prev}->assemble() . $assembled
711 0 0         if $self->{prev};
712              
713 0           return $assembled
714             }
715              
716             sub _assemble
717             {
718 0     0     return ''
719             }
720             ##------------------------------------------------------------------------------
721             package sqlSelectFrom;
722              
723 1     1   329 use strict;
  1         1  
  1         21  
724 1     1   3 use warnings;
  1         2  
  1         25  
725 1     1   4 use base 'sqlSelectAssemble';
  1         2  
  1         603  
726 1     1   5 use Scalar::Util qw(blessed);
  1         2  
  1         620  
727              
728             sub new
729             {
730 0 0   0     my $class = ref $_[0] ? ref shift : shift;
731 0           my (%args) = @_;
732 0           my (@fields);
733              
734 0           @fields = @{$args{fields}};
  0            
735              
736             my $self = bless {
737             queryFields => undef,
738             tables => undef,
739             params => $args{params}
740 0           }, $class;
741              
742 0           $self->{queryFields} = [$self->translateQueryFields(@fields)];
743 0           return $self
744             }
745              
746             sub from
747             {
748 0     0     my $self = shift;
749 0           $self->{tables} = [@_];
750 0           return sqlSelectJoin->new($self);
751             }
752              
753             sub translateQueryFields
754             {
755 0     0     my $self = shift;
756 0           my (@fields) = @_;
757 0           my @columns;
758              
759 0           foreach my $fieldIn (@fields)
760             {
761 0           my (@parts);
762              
763 0 0         unless ('HASH' eq ref $fieldIn) {
764 0           @parts = ($fieldIn, undef);
765             } else {
766 0           @parts = %$fieldIn;
767             }
768              
769 0           while (@parts) {
770 0           my ($field,$alias) = (splice @parts, 0, 2);
771              
772 0 0 0       if (blessed $field && $field->isa('sqlParameter'))
773             {
774 0 0         push @columns, $sqlQuery::PARAMETER_PLACEHOLDER
775             unless $alias;
776 0 0         push @columns, sprintf '%s AS %s',
777             $sqlQuery::PARAMETER_PLACEHOLDER,
778             sqlQuery::quoteTable($alias)
779             if $alias;
780 0           $self->addBoundArgs($field);
781 0           next;
782             }
783              
784 0 0 0       $field = sqlQuery::quoteWhenTable($field)
785             if '*' ne $field && 0 == ~index $field, ' ';
786              
787 0 0         unless ($alias) {
788 0           push @columns, $field
789             } else {
790 0 0         $alias = sqlQuery::quoteWhenTable($alias)
791             unless ~index $alias, ' ';
792 0           push @columns, "\n\t$field AS $alias";
793             }
794             }
795             }
796              
797             return @columns
798 0           }
799              
800             sub _assemble
801             {
802 0     0     my $self = shift;
803 0           my $s = 'SELECT';
804              
805 0           $s .= ' ' . join ',', @{$self->{params}}
806 0 0         if @{$self->{params}};
  0            
807 0           $s .= ' ' . join ',', @{$self->{queryFields}};
  0            
808              
809 0 0         if (defined $self->{tables}) {
810 0           $s .= "\nFROM ";
811 0           my @t;
812              
813 0           foreach my $tableSpec (@{$self->{tables}}) {
  0            
814 0           my (@tables);
815              
816 0 0         if ('HASH' eq ref $tableSpec) {
817 0           @tables = %$tableSpec;
818             } else {
819 0           @tables = ($tableSpec,undef);
820             }
821              
822 0           while (@tables) {
823 0           my ($table,$alias) = (splice @tables, 0, 2);
824              
825 0 0         push @t, sqlQuery::quoteTable($table)
826             unless $alias;
827 0 0         push @t, sqlQuery::quoteTable($table)." AS `$alias`"
828             if $alias;
829             }
830             }
831              
832 0           $s .= join ',', @t;
833             }
834              
835 0           return "$s\n";
836             }
837             ##------------------------------------------------------------------------------
838             package sqlSelectLimit;
839              
840 1     1   5 use strict;
  1         1  
  1         19  
841 1     1   4 use base 'sqlSelectAssemble';
  1         1  
  1         556  
842              
843             sub new
844             {
845 0     0     return sqlSelectAssemble::new(@_, 'sqlSelectOrderBy',
846             limit => undef,
847             offset => undef);
848             }
849              
850             sub limit
851             {
852 0     0     my $self = shift;
853              
854 0 0 0       if (!@_ || (1 == @_ && !defined $_[0]) || (2 == @_ && !defined $_[0] && !defined $_[1])) {
      0        
      0        
      0        
      0        
855 0           $self->{limit} = undef;
856             } else {
857 0           $self->{limit} = int shift;
858 0 0         $self->{offset} = int shift if @_;
859             }
860 0           return sqlSelectAssemble->new($self);
861             }
862              
863             sub _assemble
864             {
865 0     0     my $self = shift;
866 0           my $s;
867              
868 0 0         unless (defined $self->{limit}) {
    0          
869 0           $s = '';
870             } elsif (defined $self->{offset}) {
871 0           $s = "LIMIT $self->{offset},$self->{limit}";
872             } else {
873 0           $s = "LIMIT $self->{limit}";
874             }
875              
876 0           return $s
877             }
878             ##------------------------------------------------------------------------------
879             package sqlSelectOrderBy;
880              
881 1     1   4 use strict;
  1         2  
  1         18  
882 1     1   4 use base 'sqlSelectLimit';
  1         7  
  1         836  
883              
884             sub new
885             {
886 0     0     return sqlSelectAssemble::new(@_, 'sqlSelectHaving', ordering => undef);
887             }
888              
889             sub orderBy
890             {
891 0     0     my $self = shift;
892 0           $self->{ordering} = [@_];
893 0           return sqlSelectLimit->new($self);
894             }
895              
896             sub _assemble
897             {
898 0     0     my $self = shift;
899 0           my $s;
900              
901 0 0         unless(defined $self->{ordering}) {
902 0           $s = '';
903             } else {
904 0           $s = [];
905              
906 0           foreach my $order (@{$self->{ordering}}) {
  0            
907 0           my ($theOrder,$direction) = ($order);
908 0 0         if ('HASH' eq ref $theOrder) {
909 0           ($direction) = values %$theOrder;
910 0           ($theOrder) = keys %$theOrder;
911             }
912              
913 0 0         push @$s, sqlQuery::quoteWhenTable($theOrder)
914             unless $direction;
915 0 0         push @$s, sqlQuery::quoteWhenTable($theOrder)." $direction"
916             if $direction;
917             }
918              
919 0           $s = join ',', @$s;
920 0           $s = "ORDER BY $s\n";
921             }
922              
923 0           return $s . $self->SUPER::_assemble();
924             }
925             ##------------------------------------------------------------------------------
926             package sqlSelectHaving;
927              
928 1     1   5 use strict;
  1         1  
  1         29  
929 1     1   4 use base 'sqlSelectOrderBy';
  1         2  
  1         568  
930 1     1   4 use Carp 'confess';
  1         2  
  1         256  
931              
932             sub new
933             {
934 0     0     return sqlSelectAssemble::new(@_, 'sqlSelectGroupBy', havingCond => undef);
935             }
936              
937             sub having
938             {
939 0     0     my $self = shift;
940 0           my $condition = shift;
941              
942 0 0 0       confess 'Invalid condition'
943             unless ref $condition && $condition->isa('sqlCondition');
944              
945 0           $self->{havingCond} = $condition;
946 0           return sqlSelectOrderBy->new($self);
947             }
948              
949             sub gatherConditionArgs
950             {
951 0     0     my $self = shift;
952 0           my @args;
953              
954             push @args, $self->{havingCond}->getBoundArgs()
955 0 0         if $self->{havingCond};
956             return @args
957 0           }
958              
959             sub _assemble
960             {
961 0     0     my $self = shift;
962 0           my $s;
963              
964 0 0 0       unless (defined $self->{havingCond} && defined($s = $self->{havingCond}->assemble())) {
965 0           $s = '';
966             } else {
967 0           $s = "HAVING $s\n";
968             }
969              
970 0           return $s . $self->SUPER::_assemble();
971             }
972             ##------------------------------------------------------------------------------
973             package sqlSelectGroupBy;
974              
975 1     1   5 use strict;
  1         1  
  1         18  
976 1     1   4 use base 'sqlSelectHaving';
  1         1  
  1         583  
977 1     1   5 use overload '+' => 'union';
  1         2  
  1         3  
978              
979             sub union
980             {
981 0     0     my ($lhs,$rhs) = @_;
982              
983 0           return "($lhs) UNION ($rhs)";
984             }
985              
986             sub new
987             {
988 0     0     return sqlSelectAssemble::new(@_, 'sqlSelectWhere', grouping => undef);
989             }
990              
991             sub groupBy
992             {
993 0     0     my $self = shift;
994 0           $self->{grouping} = [@_];
995 0           return sqlSelectHaving->new($self);
996             }
997              
998             sub _assemble
999             {
1000 0     0     my $self = shift;
1001 0           my $s = '';
1002              
1003 0 0         if (defined $self->{grouping})
1004             {
1005 0           $s = join ',', map {sqlQuery::quoteWhenTable($_)} @{$self->{grouping}};
  0            
  0            
1006 0           $s = "GROUP BY $s\n";
1007             }
1008              
1009 0           return $s . $self->SUPER::_assemble();
1010             }
1011             ##------------------------------------------------------------------------------
1012             package sqlSelectWhere;
1013              
1014 1     1   250 use strict;
  1         2  
  1         25  
1015 1     1   10 use base 'sqlSelectGroupBy';
  1         1  
  1         560  
1016 1     1   4 use Carp 'confess';
  1         2  
  1         215  
1017              
1018             sub where
1019             {
1020 0     0     my $self = shift;
1021 0           my $condition = shift;
1022              
1023 0 0 0       confess 'Invalid condition'
      0        
1024             if defined $condition && !(ref $condition && $condition->isa('sqlCondition'));
1025              
1026 0           $self->{whereCond} = $condition;
1027 0           return sqlSelectGroupBy->new($self);
1028             }
1029              
1030             sub gatherConditionArgs
1031             {
1032 0     0     my $self = shift;
1033 0           my @args;
1034              
1035             push @args, $self->{whereCond}->getBoundArgs()
1036 0 0         if $self->{whereCond};
1037             return @args
1038 0           }
1039              
1040             sub _assemble
1041             {
1042 0     0     my $self = shift;
1043 0           my ($s,$c) = ('');
1044              
1045 0 0 0       if ($self->{whereCond} && defined($c = $self->{whereCond}->assemble()))
1046             {
1047 0           $s = "WHERE $c\n";
1048             }
1049 0           return $s . $self->SUPER::_assemble();
1050             }
1051             ##------------------------------------------------------------------------------
1052             package sqlSelectJoin;
1053              
1054 1     1   5 use strict;
  1         1  
  1         19  
1055 1     1   3 use base 'sqlSelectWhere';
  1         2  
  1         547  
1056              
1057 1     1   67 use Carp qw(confess);
  1         2  
  1         546  
1058              
1059             sub new
1060             {
1061 0     0     return sqlSelectAssemble::new(@_, 'sqlSelectFrom', joins => []);
1062             }
1063              
1064             sub gatherConditionArgs
1065             {
1066 0     0     my $self = shift;
1067 0           my (@args);
1068              
1069 0 0         if ($self->isa('sqlSelectJoin')) {
1070 0           foreach my $join (@{$self->{joins}}) {
  0            
1071 0           my ($type,$table,$condition) = @$join;
1072 0 0         push @args, $condition->getBoundArgs()
1073             if ref $condition;
1074             }
1075             }
1076              
1077 0           return (@args, $self->SUPER::gatherConditionArgs())
1078             }
1079              
1080 0     0     sub innerJoin {return shift->_addJoin('INNER', @_)}
1081 0     0     sub rightJoin {return shift->_addJoin('RIGHT', @_)}
1082 0     0     sub leftJoin {return shift->_addJoin('LEFT', @_)}
1083              
1084             sub _addJoin
1085             {
1086 0     0     my $self = shift;
1087 0           my ($type,$table,$condition) = @_;
1088 0           push @{$self->{joins}}, [$type, $table, $condition];
  0            
1089              
1090 0           return $self
1091             }
1092              
1093             sub _assemble
1094             {
1095 0     0     my $self = shift;
1096 0           my $s;
1097              
1098 0 0         unless ($self->isa('sqlSelectJoin')) {
1099 0           $s = ref $self;
1100             } else {
1101 0           $s = [];
1102              
1103 0           foreach my $join (@{$self->{joins}}) {
  0            
1104 0           my ($type, $table, $condition) = @$join;
1105 0           $table = sqlQuery::quoteTable($table);
1106 0           my $j = "$type JOIN $table ";
1107              
1108 0 0         unless (ref $condition) {
    0          
1109 0           $j .= "USING(`$condition`)";
1110             } elsif ($condition->isa('sqlCondition')) {
1111 0           $_ = $condition->assemble();
1112 0           $j .= "ON($_)";
1113             } else {
1114 0           confess sprintf 'Cannot use argument "%s" as join condition', $condition;
1115             }
1116              
1117 0           push @$s, "$j\n";
1118             }
1119              
1120 0           $s = join '', @$s;
1121             }
1122              
1123 0           return $s . $self->SUPER::_assemble();
1124             }
1125             ##------------------------------------------------------------------------------
1126             package sqlCondition;
1127              
1128 1     1   5 use strict;
  1         9  
  1         17  
1129 1     1   4 use warnings;
  1         2  
  1         40  
1130 1     1   847 no if $] >= 5.017011, warnings => 'experimental::smartmatch';
  1         8  
  1         5  
1131 1     1   66 use feature 'switch';
  1         2  
  1         108  
1132             use overload
1133 1         5 '""' => 'assemble',
1134             '+' => 'overloadAdd',
1135             '!' => 'overloadNot',
1136             '&' => 'overloadAnd',
1137 1     1   4 '|' => 'overloadOr';
  1         2  
1138              
1139 1     1   90 use constant TYPE_DEFAULT => 1;
  1         1  
  1         56  
1140 1     1   4 use constant TYPE_CONNECT_AND => 2;
  1         1  
  1         56  
1141 1     1   5 use constant TYPE_CONNECT_OR => 3;
  1         1  
  1         45  
1142 1     1   5 use constant TYPE_UNARY_NOT => 4;
  1         6  
  1         47  
1143 1     1   5 use Carp qw(confess cluck);
  1         1  
  1         77  
1144 1     1   6 use Params::Validate qw(:all);
  1         1  
  1         217  
1145 1     1   4 use Scalar::Util qw(blessed);
  1         2  
  1         2385  
1146              
1147             sub new
1148             {
1149 0 0   0     my $class = ref $_[0] ? ref shift : shift;
1150 0           my $self = bless{
1151             parent => undef,
1152             type => shift,
1153             _parts => undef,
1154             _condition => undef,
1155             _alterForNull => undef,
1156             _argument => undef,
1157             _queryArguments => []
1158             }, $class;
1159              
1160 0 0         if (TYPE_UNARY_NOT == $self->{type})
1161             {
1162 0           $self->{_argument} = shift;
1163             confess 'Invalid argument' unless
1164 0 0 0       ref $self->{_argument} && $self->{_argument}->isa('sqlCondition');
1165 0           $self->{_argument}->setParent($self);
1166             }
1167              
1168 0           return $self
1169             }
1170              
1171             sub assemble
1172             {
1173 0     0     my $self = shift;
1174              
1175 0           given($self->{type}) {
1176              
1177 0           when([TYPE_CONNECT_AND, TYPE_CONNECT_OR]) {
1178 0 0         return unless $self->{_parts};
1179 0 0         my ($glue) = (TYPE_CONNECT_AND == $self->{type} ? ' AND ' : ' OR ');
1180 0           return '('.join($glue, map {$_->assemble()} @{$self->{_parts}}).')';
  0            
  0            
1181             }
1182              
1183 0           when([TYPE_DEFAULT]) {
1184             return $self->{_condition}
1185 0 0 0       unless ref $self->{_condition} && $self->{_condition}->isa('sqlCondition');
1186 0           return $self->{_condition}->assemble();
1187             }
1188              
1189 0           when([TYPE_UNARY_NOT]) {
1190 0           $_ = $self->{_argument}->assemble();
1191 0           return "NOT($_)";
1192             }
1193             }
1194              
1195             return
1196 0           }
1197              
1198             sub overloadAdd
1199             {
1200 0     0     my ($lhs,$rhs,$leftConstant) = @_;
1201              
1202 0 0         cluck 'sqlCondition + sqlCondition will modify the left operand'
1203             if defined $leftConstant;
1204 0           return $lhs->add($rhs);
1205             }
1206              
1207             sub getOverloadArgs
1208             {
1209 0     0     my ($lhs,$rhs,$swap) = @_;
1210              
1211 0 0         ($lhs,$rhs) = ($rhs,$lhs) if $swap;
1212              
1213 0 0         $lhs = sqlCondition::C($lhs) unless ref $lhs;
1214 0 0         $rhs = sqlCondition::C($rhs) unless ref $rhs;
1215              
1216 0 0 0       confess 'Illegal LHS operand' unless blessed($lhs) && $lhs->isa('sqlCondition');
1217 0 0 0       confess 'Illegal RHS operand' unless blessed($rhs) && $rhs->isa('sqlCondition');
1218              
1219 0           return ($lhs,$rhs);
1220             }
1221              
1222             sub overloadAnd
1223             {
1224 0     0     my ($lhs,$rhs) = getOverloadArgs(@_);
1225 0           return sqlCondition::AND($lhs, $rhs);
1226             }
1227              
1228             sub overloadNot
1229             {
1230 0     0     return sqlCondition::NOT($_[0]);
1231             }
1232              
1233             sub overloadOr
1234             {
1235 0     0     my ($lhs,$rhs) = getOverloadArgs(@_);
1236 0           return sqlCondition::OR($lhs, $rhs);
1237             }
1238              
1239             sub add
1240             {
1241 0     0     my $self = shift;
1242 0 0         $self->{_parts} = [] unless $self->{_parts};
1243              
1244 0           push @{$self->{_parts}}, @_;
  0            
1245 0           $_->setParent($self) foreach @_;
1246              
1247 0           return $self
1248             }
1249              
1250             sub addSql
1251             {
1252 0     0     my $self = shift;
1253 0           my $format = shift;
1254              
1255 0           return $self->add(C(sprintf $format, @_));
1256             }
1257              
1258             sub bind
1259             {
1260 0     0     my $self = shift;
1261              
1262 0 0 0       if (1 == scalar @_ && !defined $_[0] && defined $self->{_alterForNull}) {
      0        
1263 0           ($self->{_condition}) = (split / /, $self->{_condition}, 2);
1264 0 0         $self->{_condition} .= ' IS '.($self->{_alterForNull} ? '' : 'NOT ').'NULL';
1265 0           return $self;
1266             }
1267              
1268             $self->_bind(sqlQuery::convertArgument($_))
1269 0           foreach (@_);
1270 0           return $self
1271             }
1272              
1273             sub getBoundArgs
1274             {
1275 0     0     return @{shift->{_queryArguments}};
  0            
1276             }
1277              
1278             sub releaseBoundArgs
1279             {
1280 0     0     my $self = shift;
1281 0           my @args = $self->getBoundArgs();
1282 0           $self->{_queryArguments} = [];
1283 0           return @args;
1284             }
1285              
1286 0     0     sub _OR {goto &OR}
1287             sub OR
1288             {
1289 0 0   0     confess 'OR() expects at least 1 parameter.' unless @_;
1290 0           return connectedList(TYPE_CONNECT_OR, @_);
1291             }
1292              
1293 0     0     sub _AND {goto &AND}
1294             sub AND
1295             {
1296 0 0   0     confess 'AND() expects at least 1 parameter.' unless @_;
1297 0           return connectedList(TYPE_CONNECT_AND, @_);
1298             }
1299              
1300             sub NOT
1301             {
1302 0     0     return sqlCondition->new(TYPE_UNARY_NOT, @_);
1303             }
1304              
1305             sub C
1306             {
1307 0     0     my $cond = sqlCondition->new(TYPE_DEFAULT);
1308              
1309 0 0         if (1 == scalar @_) {
1310 0           $cond->{_condition} = shift;
1311             } else {
1312 0           $cond->{_condition} = sprintf $_[0], @_[1..$#_];
1313             }
1314              
1315 0           return $cond
1316             }
1317              
1318             sub IN
1319             {
1320 0     0     my $column = shift;
1321 0           return C("%s IN($sqlQuery::PARAMETER_PLACEHOLDER)", sqlQuery::quoteWhenTable($column));
1322             }
1323              
1324             sub NOTIN
1325             {
1326 0     0     my $column = shift;
1327 0           return C("%s NOT IN($sqlQuery::PARAMETER_PLACEHOLDER)", sqlQuery::quoteWhenTable($column));
1328             }
1329              
1330             sub LIKE
1331             {
1332 0     0     my ($column,$pattern) = validate_pos @_,
1333             {column => {type => SCALAR}},
1334             {pattern => {type => SCALAR}};
1335              
1336 0           $pattern =~ s/"/"\"/g;
1337 0           $column = sqlQuery::quoteWhenTable($column);
1338 0           return C("$column LIKE \"$pattern\"");
1339             }
1340              
1341             sub BETWEEN
1342             {
1343 0     0     my ($column,$start,$end) = validate_pos @_,
1344             {column => {type => SCALAR}},
1345             {start => {isa => 'sqlParameter'}},
1346             {end => {isa => 'sqlParameter'}};
1347 0           $column = sqlQuery::quoteWhenTable($column);
1348              
1349 0           return C("$column BETWEEN $sqlQuery::PARAMETER_PLACEHOLDER AND $sqlQuery::PARAMETER_PLACEHOLDER")
1350             ->bind($start)->bind($end);
1351             }
1352              
1353             sub ISNULL
1354             {
1355 0     0     my ($column) = validate_pos @_,
1356             {column => {type => SCALAR}};
1357 0           $column = sqlQuery::quoteWhenTable($column);
1358              
1359 0           return C("$column IS NULL")
1360             }
1361              
1362             sub ISNOTNULL
1363             {
1364 0     0     my ($column) = validate_pos @_,
1365             {column => {type => SCALAR}};
1366 0           $column = sqlQuery::quoteWhenTable($column);
1367              
1368 0           return C("$column IS NOT NULL")
1369             }
1370              
1371             sub EQ
1372             {
1373 0     0     my $cond = _OP('=', @_);
1374 0           $cond->{_alterForNull} = 1;
1375 0           return $cond
1376             }
1377              
1378             sub NE
1379             {
1380 0     0     my $cond = _OP('!=', @_);
1381 0           $cond->{_alterForNull} = 0;
1382 0           return $cond
1383             }
1384              
1385 0     0     sub LT {return _OP('<', @_)}
1386 0     0     sub GT {return _OP('>', @_)}
1387 0     0     sub LTE {return _OP('<=', @_)}
1388 0     0     sub GTE {return _OP('>=', @_)}
1389              
1390             sub _OP
1391             {
1392 0     0     my ($operator, $lhs, $rhs) = @_;
1393 0 0         return C('%s %s %s',
1394             sqlQuery::quoteWhenTable($lhs),
1395             $operator,
1396             3 != scalar @_
1397             ? $sqlQuery::PARAMETER_PLACEHOLDER
1398             : sqlQuery::quoteWhenTable($rhs));
1399             }
1400              
1401             sub connectedList
1402             {
1403 0     0     my $type = shift;
1404 0           my $cond = sqlCondition->new($type);
1405              
1406 0           foreach my $a (@_) {
1407 0 0 0       unless (blessed($a) && $a->isa('sqlCondition')) {
1408 0           $cond->insert($a);
1409 0           next;
1410             }
1411              
1412 0 0         if ($a->{type} != $type) {
1413 0           $cond->insert($a);
1414 0           next;
1415             }
1416              
1417 0           $cond->_bind($_) foreach $a->releaseBoundArgs();
1418 0           $cond->insert(@{$a->{_parts}});
  0            
1419             }
1420              
1421 0           return $cond
1422             }
1423              
1424             sub insert
1425             {
1426 0     0     my $self = shift;
1427              
1428 0 0         $self->{_parts} = [] unless $self->{_parts};
1429 0           return $self->add(@_);
1430             }
1431              
1432             sub _bind
1433             {
1434 0     0     my $self = shift;
1435 0           my ($parameter) = validate_pos @_,
1436             {parameter => {isa => 'sqlParameter'}};
1437              
1438 0           push @{$self->{_queryArguments}}, $parameter
1439 0 0         unless $self->{parent};
1440             $self->{parent}->up()->_bind($parameter)
1441 0 0         if $self->{parent};
1442 0           return $self
1443             }
1444              
1445             sub setParent
1446             {
1447 0     0     my $self = shift;
1448 0           my ($parent) = validate_pos @_,
1449             {parameter => {isa => 'sqlCondition'}};
1450              
1451 0           $self->{parent} = $parent;
1452             $self->{parent}->up()->_bind($_)
1453 0           foreach @{$self->{_queryArguments}};
  0            
1454 0           $self->{_queryArguments} = [];
1455 0           return $self
1456             }
1457              
1458             sub up
1459             {
1460 0     0     my $self = shift;
1461              
1462             return $self
1463 0 0         unless defined $self->{parent};
1464 0           return $self->{parent}->up();
1465             }
1466              
1467             1;
1468             __END__