]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Reporter/SQLBuilder.pm
clark is operational! and, adding relative_time to the resultset object for rel-time...
[Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Reporter / SQLBuilder.pm
1 #-------------------------------------------------------------------------------------------------
2 package OpenILS::Reporter::SQLBuilder;
3
4 sub new {
5         my $class = shift;
6         $class = ref($class) || $class;
7
8         return bless { _sql => undef } => $class;
9 }
10
11 sub register_params {
12         my $self  = shift;
13         my $p = shift;
14         $self->{_params} = $p;
15 }
16
17 sub get_param {
18         my $self = shift;
19         my $p = shift;
20         return $self->{_builder}->{_params}->{$p};
21 }
22
23 sub set_builder {
24         my $self = shift;
25         $self->{_builder} = shift;
26         return $self;
27 }
28
29 sub builder {
30         my $self = shift;
31         return $self->{_builder};
32 }
33
34 sub relative_time {
35         my $self = shift;
36         my $t = shift;
37         $self->builder->{_relative_time} = $t if (defined $t);
38         return $self->builder->{_relative_time};
39 }
40
41 sub resolve_param {
42         my $self = shift;
43         my $val = shift;
44
45         if (defined($val) && $val =~ /^::(.+)$/o) {
46                 $val = $self->get_param($1);
47         }
48
49         if (defined($val) && !ref($val)) {
50                 $val =~ s/\\/\\\\/go;
51                 $val =~ s/"/\\"/go;
52         }
53
54         return $val;
55 }
56
57 sub parse_report {
58         my $self = shift;
59         my $report = shift;
60
61         my $rs = OpenILS::Reporter::SQLBuilder::ResultSet->new;
62
63         $rs->is_subquery( 1 ) if ( $report->{alias} );
64
65         $rs     ->set_builder( $self )
66                 ->set_subquery_alias( $report->{alias} )
67                 ->set_select( $report->{select} )
68                 ->set_from( $report->{from} )
69                 ->set_where( $report->{where} )
70                 ->set_having( $report->{having} )
71                 ->set_order_by( $report->{order_by} )
72                 ->set_pivot_data( $report->{pivot_data} )
73                 ->set_pivot_label( $report->{pivot_label} )
74                 ->set_pivot_default( $report->{pivot_default} );
75
76         return $rs;
77 }
78
79
80 #-------------------------------------------------------------------------------------------------
81 package OpenILS::Reporter::SQLBuilder::ResultSet;
82 use base qw/OpenILS::Reporter::SQLBuilder/;
83
84 sub is_subquery {
85         my $self = shift;
86         my $flag = shift;
87         $self->{_is_subquery} = $flag if (defined $flag);
88         return $self->{_is_subquery};
89 }
90
91 sub pivot_data {
92         my $self = shift;
93         return $self->{_pivot_data};
94 }
95
96 sub pivot_label {
97         my $self = shift;
98         return $self->{_pivot_label};
99 }
100
101 sub pivot_default {
102         my $self = shift;
103         return $self->{_pivot_label};
104 }
105
106 sub set_pivot_default {
107         my $self = shift;
108         my $p = shift;
109         $self->{_pivot_default} = $p if (defined $p);
110         return $self;
111 }
112
113 sub set_pivot_data {
114         my $self = shift;
115         my $p = shift;
116         $self->{_pivot_data} = $p if (defined $p);
117         return $self;
118 }
119
120 sub set_pivot_label {
121         my $self = shift;
122         my $p = shift;
123         $self->{_pivot_label} = $p if (defined $p);
124         return $self;
125 }
126
127 sub set_subquery_alias {
128         my $self = shift;
129         my $alias = shift;
130         $self->{_alias} = $alias if (defined $alias);
131         return $self;
132 }
133
134 sub set_select {
135         my $self = shift;
136         my @cols = @_;
137
138         $self->{_select} = [];
139
140         return $self unless (@cols && defined($cols[0]));
141         @cols = @{ $cols[0] } if (@cols == 1 && ref($cols[0]) eq 'ARRAY');
142
143         push @{ $self->{_select} }, map { OpenILS::Reporter::SQLBuilder::Column::Select->new( $_ )->set_builder( $self->builder ) } @cols;
144
145         return $self;
146 }
147
148 sub set_from {
149         my $self = shift;
150         my $f = shift;
151
152         $self->{_from} = OpenILS::Reporter::SQLBuilder::Relation->parse( $f );
153
154         return $self;
155 }
156
157 sub set_where {
158         my $self = shift;
159         my @cols = @_;
160
161         $self->{_where} = [];
162
163         return $self unless (@cols && defined($cols[0]));
164         @cols = @{ $cols[0] } if (@cols == 1 && ref($cols[0]) eq 'ARRAY');
165
166         push @{ $self->{_where} }, map { OpenILS::Reporter::SQLBuilder::Column::Where->new( $_ )->set_builder( $self->builder ) } @cols;
167
168         return $self;
169 }
170
171 sub set_having {
172         my $self = shift;
173         my @cols = @_;
174
175         $self->{_having} = [];
176
177         return $self unless (@cols && defined($cols[0]));
178         @cols = @{ $cols[0] } if (@cols == 1 && ref($cols[0]) eq 'ARRAY');
179
180         push @{ $self->{_having} }, map { OpenILS::Reporter::SQLBuilder::Column::Having->new( $_ )->set_builder( $self->builder ) } @cols;
181
182         return $self;
183 }
184
185 sub set_order_by {
186         my $self = shift;
187         my @cols = @_;
188
189         $self->{_order_by} = [];
190
191         return $self unless (@cols && defined($cols[0]));
192         @cols = @{ $cols[0] } if (@cols == 1 && ref($cols[0]) eq 'ARRAY');
193
194         push @{ $self->{_order_by} }, map { OpenILS::Reporter::SQLBuilder::Column::OrderBy->new( $_ )->set_builder( $self->builder ) } @cols;
195
196         return $self;
197 }
198
199 sub column_label_list {
200         my $self = shift;
201
202         my @labels;
203         push @labels, $self->resolve_param( $_->{_alias} ) for ( @{ $self->{_select} } );
204         return @labels;
205 }
206
207 sub group_by_list {
208         my $self = shift;
209         my $base = shift;
210         $base = 1 unless (defined $base);
211
212         my $gcount = $base;
213         my @group_by;
214         for my $c ( @{ $self->{_select} } ) {
215                 push @group_by, $gcount if (!$c->is_aggregate);
216                 $gcount++;
217         }
218
219         return @group_by;
220 }
221
222 sub toSQL {
223         my $self = shift;
224
225         return $self->{_sql} if ($self->{_sql});
226
227         my $sql = '';
228
229         if ($self->is_subquery) {
230                 $sql = '(';
231         }
232
233         $sql .= "SELECT\t" . join(",\n\t", map { $_->toSQL } @{ $self->{_select} }) . "\n" if (@{ $self->{_select} });
234         $sql .= "  FROM\t" . $self->{_from}->toSQL . "\n" if ($self->{_from});
235         $sql .= "  WHERE\t" . join("\n\tAND ", map { $_->toSQL } @{ $self->{_where} }) . "\n" if (@{ $self->{_where} });
236
237         my @group_by = $self->group_by_list;
238
239         $sql .= '  GROUP BY ' . join(', ', @group_by) . "\n" if (@group_by);
240         $sql .= "  HAVING " . join("\n\tAND ", map { $_->toSQL } @{ $self->{_having} }) . "\n" if (@{ $self->{_having} });
241         $sql .= '  ORDER BY ' . join(', ', map { $_->toSQL } @{ $self->{_order_by} }) . "\n" if (@{ $self->{_order_by} });
242
243         if ($self->is_subquery) {
244                 $sql .= ') '. $self->{_alias} . "\n";
245         }
246
247         return $self->{_sql} = $sql;
248 }
249
250
251 #-------------------------------------------------------------------------------------------------
252 package OpenILS::Reporter::SQLBuilder::Input;
253 use base qw/OpenILS::Reporter::SQLBuilder/;
254
255 sub new {
256         my $class = shift;
257         my $self = $class->SUPER::new;
258
259         my $col_data = shift;
260
261         if (ref($col_data)) {
262                 $self->{params} = $col_data->{params};
263                 my $trans = $col_data->{transform} || 'Bare';
264                 my $pkg = "OpenILS::Reporter::SQLBuilder::Input::Transform::$trans";
265                 if (UNIVERSAL::can($pkg => 'toSQL')) {
266                         $self->{_transform} = $trans;
267                 } else {
268                         $self->{_transform} = 'GenericTransform';
269                 }
270         } elsif( defined($col_data) ) {
271                 $self->{_transform} = 'Bare';
272                 $self->{params} = $col_data;
273         } else {
274                 $self->{_transform} = 'NULL';
275         }
276
277
278
279         return $self;
280 }
281
282 sub toSQL {
283         my $self = shift;
284         my $type = $self->{_transform};
285         return $self->{_sql} if ($self->{_sql});
286         my $toSQL = "OpenILS::Reporter::SQLBuilder::Input::Transform::${type}::toSQL";
287         return $self->{_sql} = $self->$toSQL;
288 }
289
290
291 #-------------------------------------------------------------------------------------------------
292 package OpenILS::Reporter::SQLBuilder::Input::Transform::NULL;
293
294 sub toSQL {
295         return "NULL";
296 }
297
298
299 #-------------------------------------------------------------------------------------------------
300 package OpenILS::Reporter::SQLBuilder::Input::Transform::Bare;
301
302 sub toSQL {
303         my $self = shift;
304
305         my $val = $self->{params};
306         $val = $$val[0] if (ref($val));
307         
308         $val =~ s/\\/\\\\/go;
309         $val =~ s/'/\\'/go;
310
311         return "'$val'";
312 }
313
314
315 #-------------------------------------------------------------------------------------------------
316 package OpenILS::Reporter::SQLBuilder::Input::Transform::relative_year;
317
318 sub toSQL {
319         my $self = shift;
320
321         my $rtime = $self->relative_time || 'now';
322
323         $rtime =~ s/\\/\\\\/go;
324         $rtime =~ s/'/\\'/go;
325
326         my $val = $self->{params};
327         $val = $$val[0] if (ref($val));
328
329         $val =~ s/\\/\\\\/go;
330         $val =~ s/'/\\'/go;
331
332         return "EXTRACT(YEAR FROM '$rtime'::TIMESTAMPTZ + '$val years')";
333 }
334
335
336 #-------------------------------------------------------------------------------------------------
337 package OpenILS::Reporter::SQLBuilder::Input::Transform::relative_month;
338
339 sub toSQL {
340         my $self = shift;
341
342         my $rtime = $self->relative_time || 'now';
343
344         $rtime =~ s/\\/\\\\/go;
345         $rtime =~ s/'/\\'/go;
346
347         my $val = $self->{params};
348         $val = $$val[0] if (ref($val));
349
350         $val =~ s/\\/\\\\/go;
351         $val =~ s/'/\\'/go;
352
353         return "EXTRACT(YEAR FROM '$rtime'::TIMESTAMPTZ + '$val months')" .
354                 " || '-' || LPAD(EXTRACT(MONTH FROM '$rtime'::TIMESTAMPTZ + '$val months'),2,'0')";
355 }
356
357
358 #-------------------------------------------------------------------------------------------------
359 package OpenILS::Reporter::SQLBuilder::Input::Transform::relative_date;
360
361 sub toSQL {
362         my $self = shift;
363
364         my $rtime = $self->relative_time || 'now';
365
366         $rtime =~ s/\\/\\\\/go;
367         $rtime =~ s/'/\\'/go;
368
369         my $val = $self->{params};
370         $val = $$val[0] if (ref($val));
371
372         $val =~ s/\\/\\\\/go;
373         $val =~ s/'/\\'/go;
374
375         return "DATE('$rtime'::TIMESTAMPTZ + '$val days')";
376 }
377
378
379 #-------------------------------------------------------------------------------------------------
380 package OpenILS::Reporter::SQLBuilder::Input::Transform::relative_week;
381
382 sub toSQL {
383         my $self = shift;
384
385         my $rtime = $self->relative_time || 'now';
386
387         $rtime =~ s/\\/\\\\/go;
388         $rtime =~ s/'/\\'/go;
389
390         my $val = $self->{params};
391         $val = $$val[0] if (ref($val));
392
393         $val =~ s/\\/\\\\/go;
394         $val =~ s/'/\\'/go;
395
396         return "EXTRACT(WEEK FROM '$rtime'::TIMESTAMPTZ + '$val weeks')";
397 }
398
399
400 #-------------------------------------------------------------------------------------------------
401 package OpenILS::Reporter::SQLBuilder::Column;
402 use base qw/OpenILS::Reporter::SQLBuilder/;
403
404 sub new {
405         my $class = shift;
406         my $self = $class->SUPER::new;
407
408         my $col_data = shift;
409         $self->{_relation} = $col_data->{relation};
410         $self->{_column} = $col_data->{column};
411
412         $self->{_aggregate} = $col_data->{aggregate};
413
414         if (ref($self->{_column})) {
415                 my $trans = $self->{_column}->{transform} || 'Bare';
416                 my $pkg = "OpenILS::Reporter::SQLBuilder::Column::Transform::$trans";
417                 if (UNIVERSAL::can($pkg => 'toSQL')) {
418                         $self->{_transform} = $trans;
419                 } else {
420                         $self->{_transform} = 'GenericTransform';
421                 }
422         } elsif( defined($self->{_column}) ) {
423                 $self->{_transform} = 'Bare';
424         } else {
425                 $self->{_transform} = 'NULL';
426         }
427
428
429         return $self;
430 }
431
432 sub name {
433         my $self = shift;
434         if (ref($self->{_column})) {
435                  return $self->{_column}->{colname};
436         } else {
437                 return $self->{_column};
438         }
439 }
440
441 sub toSQL {
442         my $self = shift;
443         my $type = $self->{_transform};
444         return $self->{_sql} if ($self->{_sql});
445         my $toSQL = "OpenILS::Reporter::SQLBuilder::Column::Transform::${type}::toSQL";
446         return $self->{_sql} = $self->$toSQL;
447 }
448
449 sub is_aggregate {
450         my $self = shift;
451         my $type = $self->{_transform};
452         my $is_agg = "OpenILS::Reporter::SQLBuilder::Column::Transform::${type}::is_aggregate";
453         return $self->$is_agg;
454 }
455
456
457 #-------------------------------------------------------------------------------------------------
458 package OpenILS::Reporter::SQLBuilder::Column::OrderBy;
459 use base qw/OpenILS::Reporter::SQLBuilder::Column/;
460
461 sub new {
462         my $class = shift;
463         my $self = $class->SUPER::new(@_);
464
465         my $col_data = shift;
466         $self->{_direction} = $col_data->{direction} || 'ascending';
467         return $self;
468 }
469
470 sub toSQL {
471         my $self = shift;
472         my $dir = ($self->{_direction} =~ /^d/oi) ? 'DESC' : 'ASC';
473         return $self->{_sql} if ($self->{_sql});
474         return $self->{_sql} = $self->SUPER::toSQL .  " $dir";
475 }
476
477
478 #-------------------------------------------------------------------------------------------------
479 package OpenILS::Reporter::SQLBuilder::Column::Select;
480 use base qw/OpenILS::Reporter::SQLBuilder::Column/;
481
482 sub new {
483         my $class = shift;
484         my $self = $class->SUPER::new(@_);
485
486         my $col_data = shift;
487         $self->{_alias} = $col_data->{alias} || $self->name;
488         return $self;
489 }
490
491 sub toSQL {
492         my $self = shift;
493         return $self->{_sql} if ($self->{_sql});
494         return $self->{_sql} = $self->SUPER::toSQL .  ' AS "' . $self->resolve_param( $self->{_alias} ) . '"';
495 }
496
497
498 #-------------------------------------------------------------------------------------------------
499 package OpenILS::Reporter::SQLBuilder::Column::Transform::GenericTransform;
500
501 sub toSQL {
502         my $self = shift;
503         my $name = $self->name;
504         my ($func) = keys %{ $self->{_column} };
505
506         my @params;
507         @params = @{ $self->resolve_param( $self->{_column}->{params} ) } if ($self->{_column}->{params});
508
509         my $sql = $func . '("' . $self->{_relation} . '"."' . $self->name . '"';
510         $sql .= ",'" . join("','", @params) . "'" if (@params);
511         $sql .= ')';
512
513         return $sql;
514 }
515
516 sub is_aggregate { return $self->{_aggregate} }
517
518 #-------------------------------------------------------------------------------------------------
519 package OpenILS::Reporter::SQLBuilder::Column::Transform::Bare;
520
521 sub toSQL {
522         my $self = shift;
523         return '"' . $self->{_relation} . '"."' . $self->name . '"';
524 }
525
526 sub is_aggregate { return 0 }
527
528 #-------------------------------------------------------------------------------------------------
529 package OpenILS::Reporter::SQLBuilder::Column::Transform::substring;
530
531 sub toSQL {
532         my $self = shift;
533         my $params = $self->resolve_param( $self->{_column}->{params} );
534         my $start = $$params[0];
535         my $len = $$params[1];
536         return 'SUBSTRING("' . $self->{_relation} . '"."' . $self->name . "\",$start,$len)";
537 }
538
539 sub is_aggregate { return 0 }
540
541
542 #-------------------------------------------------------------------------------------------------
543 package OpenILS::Reporter::SQLBuilder::Column::Transform::day_name;
544
545 sub toSQL {
546         my $self = shift;
547         return 'TO_CHAR("' . $self->{_relation} . '"."' . $self->name . '", \'Day\')';
548 }
549
550 sub is_aggregate { return 0 }
551
552
553 #-------------------------------------------------------------------------------------------------
554 package OpenILS::Reporter::SQLBuilder::Column::Transform::month_name;
555
556 sub toSQL {
557         my $self = shift;
558         return 'TO_CHAR("' . $self->{_relation} . '"."' . $self->name . '", \'Month\')';
559 }
560
561 sub is_aggregate { return 0 }
562
563
564 #-------------------------------------------------------------------------------------------------
565 package OpenILS::Reporter::SQLBuilder::Column::Transform::doy;
566
567 sub toSQL {
568         my $self = shift;
569         return 'EXTRACT(DOY FROM "' . $self->{_relation} . '"."' . $self->name . '")';
570 }
571
572 sub is_aggregate { return 0 }
573
574
575 #-------------------------------------------------------------------------------------------------
576 package OpenILS::Reporter::SQLBuilder::Column::Transform::woy;
577
578 sub toSQL {
579         my $self = shift;
580         return 'EXTRACT(WEEK FROM "' . $self->{_relation} . '"."' . $self->name . '")';
581 }
582
583 sub is_aggregate { return 0 }
584
585
586 #-------------------------------------------------------------------------------------------------
587 package OpenILS::Reporter::SQLBuilder::Column::Transform::moy;
588
589 sub toSQL {
590         my $self = shift;
591         return 'EXTRACT(MONTH FROM "' . $self->{_relation} . '"."' . $self->name . '")';
592 }
593
594 sub is_aggregate { return 0 }
595
596
597 #-------------------------------------------------------------------------------------------------
598 package OpenILS::Reporter::SQLBuilder::Column::Transform::qoy;
599
600 sub toSQL {
601         my $self = shift;
602         return 'EXTRACT(QUARTER FROM "' . $self->{_relation} . '"."' . $self->name . '")';
603 }
604
605 sub is_aggregate { return 0 }
606
607
608 #-------------------------------------------------------------------------------------------------
609 package OpenILS::Reporter::SQLBuilder::Column::Transform::dom;
610
611 sub toSQL {
612         my $self = shift;
613         return 'EXTRACT(DAY FROM "' . $self->{_relation} . '"."' . $self->name . '")';
614 }
615
616 sub is_aggregate { return 0 }
617
618
619 #-------------------------------------------------------------------------------------------------
620 package OpenILS::Reporter::SQLBuilder::Column::Transform::dow;
621
622 sub toSQL {
623         my $self = shift;
624         return 'EXTRACT(DOW FROM "' . $self->{_relation} . '"."' . $self->name . '")';
625 }
626
627 sub is_aggregate { return 0 }
628
629
630 #-------------------------------------------------------------------------------------------------
631 package OpenILS::Reporter::SQLBuilder::Column::Transform::year_trunc;
632
633 sub toSQL {
634         my $self = shift;
635         return 'EXTRACT(YEAR FROM "' . $self->{_relation} . '"."' . $self->name . '")';
636 }
637
638 sub is_aggregate { return 0 }
639
640
641 #-------------------------------------------------------------------------------------------------
642 package OpenILS::Reporter::SQLBuilder::Column::Transform::month_trunc;
643
644 sub toSQL {
645         my $self = shift;
646         return 'EXTRACT(YEAR FROM "' . $self->{_relation} . '"."' . $self->name . '")' .
647                 ' || \'-\' || LPAD(EXTRACT(MONTH FROM "' . $self->{_relation} . '"."' . $self->name . '"),2,\'0\')';
648 }
649
650 sub is_aggregate { return 0 }
651
652
653 #-------------------------------------------------------------------------------------------------
654 package OpenILS::Reporter::SQLBuilder::Column::Transform::quarter;
655
656 sub toSQL {
657         my $self = shift;
658         return 'EXTRACT(YEAR FROM "' . $self->{_relation} . '"."' . $self->name . '")' .
659                 ' || \'-Q\' || EXTRACT(QUARTER FROM "' . $self->{_relation} . '"."' . $self->name . '")';
660 }
661
662 sub is_aggregate { return 0 }
663
664
665 #-------------------------------------------------------------------------------------------------
666 package OpenILS::Reporter::SQLBuilder::Column::Transform::months_ago;
667
668 sub toSQL {
669         my $self = shift;
670         return 'EXTRACT(MONTH FROM AGE(NOW(),"' . $self->{_relation} . '"."' . $self->name . '"))';
671 }
672
673 sub is_aggregate { return 0 }
674
675
676 #-------------------------------------------------------------------------------------------------
677 package OpenILS::Reporter::SQLBuilder::Column::Transform::quarters_ago;
678
679 sub toSQL {
680         my $self = shift;
681         return 'EXTRACT(QUARTER FROM AGE(NOW(),"' . $self->{_relation} . '"."' . $self->name . '"))';
682 }
683
684 sub is_aggregate { return 0 }
685
686
687 #-------------------------------------------------------------------------------------------------
688 package OpenILS::Reporter::SQLBuilder::Column::Transform::age;
689
690 sub toSQL {
691         my $self = shift;
692         return 'AGE(NOW(),"' . $self->{_relation} . '"."' . $self->name . '")';
693 }
694
695 sub is_aggregate { return 0 }
696
697
698 #-------------------------------------------------------------------------------------------------
699 package OpenILS::Reporter::SQLBuilder::Column::Transform::first;
700
701 sub toSQL {
702         my $self = shift;
703         return 'FIRST("' . $self->{_relation} . '"."' . $self->name . '")';
704 }
705
706 sub is_aggregate { return 1 }
707
708
709 #-------------------------------------------------------------------------------------------------
710 package OpenILS::Reporter::SQLBuilder::Column::Transform::last;
711
712 sub toSQL {
713         my $self = shift;
714         return 'LAST("' . $self->{_relation} . '"."' . $self->name . '")';
715 }
716
717 sub is_aggregate { return 1 }
718
719
720 #-------------------------------------------------------------------------------------------------
721 package OpenILS::Reporter::SQLBuilder::Column::Transform::min;
722
723 sub toSQL {
724         my $self = shift;
725         return 'MIN("' . $self->{_relation} . '"."' . $self->name . '")';
726 }
727
728 sub is_aggregate { return 1 }
729
730
731 #-------------------------------------------------------------------------------------------------
732 package OpenILS::Reporter::SQLBuilder::Column::Transform::max;
733
734 sub toSQL {
735         my $self = shift;
736         return 'MAX("' . $self->{_relation} . '"."' . $self->name . '")';
737 }
738
739 sub is_aggregate { return 1 }
740
741
742 #-------------------------------------------------------------------------------------------------
743 package OpenILS::Reporter::SQLBuilder::Column::Transform::count;
744
745 sub toSQL {
746         my $self = shift;
747         return 'COUNT("' . $self->{_relation} . '"."' . $self->name . '")';
748 }
749
750 sub is_aggregate { return 1 }
751
752
753 #-------------------------------------------------------------------------------------------------
754 package OpenILS::Reporter::SQLBuilder::Column::Transform::count_distinct;
755
756 sub toSQL {
757         my $self = shift;
758         return 'COUNT(DISTINCT "' . $self->{_relation} . '"."' . $self->name . '")';
759 }
760
761 sub is_aggregate { return 1 }
762
763
764 #-------------------------------------------------------------------------------------------------
765 package OpenILS::Reporter::SQLBuilder::Column::Transform::sum;
766
767 sub toSQL {
768         my $self = shift;
769         return 'SUM("' . $self->{_relation} . '"."' . $self->name . '")';
770 }
771
772 sub is_aggregate { return 1 }
773
774
775 #-------------------------------------------------------------------------------------------------
776 package OpenILS::Reporter::SQLBuilder::Column::Transform::average;
777
778 sub toSQL {
779         my $self = shift;
780         return 'AVG("' . $self->{_relation} . '"."' . $self->name .  '")';
781 }
782
783 sub is_aggregate { return 1 }
784
785
786 #-------------------------------------------------------------------------------------------------
787 package OpenILS::Reporter::SQLBuilder::Column::Where;
788 use base qw/OpenILS::Reporter::SQLBuilder::Column/;
789
790 sub new {
791         my $class = shift;
792         my $self = $class->SUPER::new(@_);
793
794         my $col_data = shift;
795         $self->{_condition} = $col_data->{condition};
796
797         return $self;
798 }
799
800 sub _flesh_conditions {
801         my $cond = shift;
802         $cond = [$cond] unless (ref($cond) eq 'ARRAY');
803
804         my @out;
805         for my $c (@$cond) {
806                 push @out, OpenILS::Reporter::SQLBuilder::Input->new( $c );
807         }
808
809         return \@out;
810 }
811
812 sub toSQL {
813         my $self = shift;
814
815         return $self->{_sql} if ($self->{_sql});
816         my $sql = $self->SUPER::toSQL;
817
818         my ($op) = keys %{ $self->{_condition} };
819         my $val = _flesh_conditions( $self->resolve_param( $self->{_condition}->{$op} ) );
820
821         if (lc($op) eq 'in') {
822                 $sql .= " IN (". join(",", map { $_->toSQL } @$val).")";
823         } elsif (lc($op) eq 'not in') {
824                 $sql .= " NOT IN (". join(",", map { $_->toSQL } @$val).")";
825         } elsif (lc($op) eq 'between') {
826                 $sql .= " BETWEEN ". join(" AND ", map { $_->toSQL } @$val);
827         } elsif (lc($op) eq 'not between') {
828                 $sql .= " NOT BETWEEN ". join(" AND ", map { $_->toSQL } @$val);
829         } else {
830                 $val = $$val[0] if (ref($val) eq 'ARRAY');
831                 $sql .= " $op " . $val->toSQL;
832         }
833
834         return $self->{_sql} = $sql;
835 }
836
837
838 #-------------------------------------------------------------------------------------------------
839 package OpenILS::Reporter::SQLBuilder::Column::Having;
840 use base qw/OpenILS::Reporter::SQLBuilder::Column::Where/;
841
842 #-------------------------------------------------------------------------------------------------
843 package OpenILS::Reporter::SQLBuilder::Relation;
844 use base qw/OpenILS::Reporter::SQLBuilder/;
845
846 sub parse {
847         my $self = shift;
848         $self = $self->SUPER::new if (!ref($self));
849
850         my $rel_data = shift;
851
852         $self->{_table} = $rel_data->{table};
853         $self->{_alias} = $rel_data->{alias} || $self->name;
854         $self->{_join} = [];
855         $self->{_columns} = [];
856
857         if ($rel_data->{join}) {
858                 $self->add_join(
859                         $_ => OpenILS::Reporter::SQLBuilder::Relation->parse( $rel_data->{join}->{$_} ) => $rel_data->{join}->{$_}->{key}
860                 ) for ( keys %{ $rel_data->{join} } );
861         }
862
863         return $self;
864 }
865
866 sub add_column {
867         my $self = shift;
868         my $col = shift;
869         
870         push @{ $self->{_columns} }, $col;
871 }
872
873 sub find_column {
874         my $self = shift;
875         my $col = shift;
876         return (grep { $_->name eq $col} @{ $self->{_columns} })[0];
877 }
878
879 sub add_join {
880         my $self = shift;
881         my $col = shift;
882         my $frel = shift;
883         my $fkey = shift;
884
885         if (ref($col) eq 'OpenILS::Reporter::SQLBuilder::Join') {
886                 push @{ $self->{_join} }, $col;
887         } else {
888                 push @{ $self->{_join} }, OpenILS::Reporter::SQLBuilder::Join->build( $self => $col, $frel => $fkey );
889         }
890
891         return $self;
892 }
893
894 sub is_join {
895         my $self = shift;
896         my $j = shift;
897         $self->{_is_join} = $j if ($j);
898         return $self->{_is_join};
899 }
900
901 sub toSQL {
902         my $self = shift;
903         return $self->{_sql} if ($self->{_sql});
904
905         my $sql = $self->{_table} .' AS "'. $self->{_alias} .'"';
906
907         if (!$self->is_join) {
908                 for my $j ( @{ $self->{_join} } ) {
909                         $sql .= $j->toSQL;
910                 }
911         }
912
913         return $self->{_sql} = $sql;
914 }
915
916 #-------------------------------------------------------------------------------------------------
917 package OpenILS::Reporter::SQLBuilder::Join;
918 use base qw/OpenILS::Reporter::SQLBuilder/;
919
920 sub build {
921         my $self = shift;
922         $self = $self->SUPER::new if (!ref($self));
923
924         $self->{_left_rel} = shift;
925         ($self->{_left_col}) = split(/-/,shift());
926
927         $self->{_right_rel} = shift;
928         $self->{_right_col} = shift;
929
930         $self->{_right_rel}->is_join(1);
931
932         return $self;
933 }
934
935 sub toSQL {
936         my $self = shift;
937         return $self->{_sql} if ($self->{_sql});
938
939         my $sql = "\n\tJOIN " . $self->{_right_rel}->toSQL .
940                 ' ON ("' . $self->{_left_rel}->{_alias} . '"."' . $self->{_left_col} .
941                 '" = "' . $self->{_right_rel}->{_alias} . '"."' . $self->{_right_col} . '")';
942
943         $sql .= $_->toSQL for (@{ $self->{_right_rel}->{_join} });
944
945         return $self->{_sql} = $sql;
946 }
947
948 1;