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