]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Reporter/SQLBuilder.pm
c2bfb9631152eeb62fdc9a0440a79df47459e625
[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::age;
321
322 sub toSQL {
323         my $self = shift;
324
325         my $val = $self->{params};
326         $val = $$val[0] if (ref($val));
327
328         $val =~ s/\\/\\\\/go;
329         $val =~ s/'/\\'/go;
330
331         return "AGE(NOW(),'" . $val . "'::TIMESTAMPTZ)";
332 }
333
334 sub is_aggregate { return 0 }
335
336
337 #-------------------------------------------------------------------------------------------------
338 package OpenILS::Reporter::SQLBuilder::Input::Transform::relative_year;
339
340 sub toSQL {
341         my $self = shift;
342
343         my $rtime = $self->relative_time || 'now';
344
345         $rtime =~ s/\\/\\\\/go;
346         $rtime =~ s/'/\\'/go;
347
348         my $val = $self->{params};
349         $val = $$val[0] if (ref($val));
350
351         $val =~ s/\\/\\\\/go;
352         $val =~ s/'/\\'/go;
353
354         return "EXTRACT(YEAR FROM '$rtime'::TIMESTAMPTZ + '$val years')";
355 }
356
357
358 #-------------------------------------------------------------------------------------------------
359 package OpenILS::Reporter::SQLBuilder::Input::Transform::relative_month;
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 "EXTRACT(YEAR FROM '$rtime'::TIMESTAMPTZ + '$val months')" .
376                 " || '-' || LPAD(EXTRACT(MONTH FROM '$rtime'::TIMESTAMPTZ + '$val months'),2,'0')";
377 }
378
379
380 #-------------------------------------------------------------------------------------------------
381 package OpenILS::Reporter::SQLBuilder::Input::Transform::relative_date;
382
383 sub toSQL {
384         my $self = shift;
385
386         my $rtime = $self->relative_time || 'now';
387
388         $rtime =~ s/\\/\\\\/go;
389         $rtime =~ s/'/\\'/go;
390
391         my $val = $self->{params};
392         $val = $$val[0] if (ref($val));
393
394         $val =~ s/\\/\\\\/go;
395         $val =~ s/'/\\'/go;
396
397         return "DATE('$rtime'::TIMESTAMPTZ + '$val days')";
398 }
399
400
401 #-------------------------------------------------------------------------------------------------
402 package OpenILS::Reporter::SQLBuilder::Input::Transform::relative_week;
403
404 sub toSQL {
405         my $self = shift;
406
407         my $rtime = $self->relative_time || 'now';
408
409         $rtime =~ s/\\/\\\\/go;
410         $rtime =~ s/'/\\'/go;
411
412         my $val = $self->{params};
413         $val = $$val[0] if (ref($val));
414
415         $val =~ s/\\/\\\\/go;
416         $val =~ s/'/\\'/go;
417
418         return "EXTRACT(WEEK FROM '$rtime'::TIMESTAMPTZ + '$val weeks')";
419 }
420
421
422 #-------------------------------------------------------------------------------------------------
423 package OpenILS::Reporter::SQLBuilder::Column;
424 use base qw/OpenILS::Reporter::SQLBuilder/;
425
426 sub new {
427         my $class = shift;
428         my $self = $class->SUPER::new;
429
430         my $col_data = shift;
431         $self->{_relation} = $col_data->{relation};
432         $self->{_column} = $col_data->{column};
433
434         $self->{_aggregate} = $col_data->{aggregate};
435
436         if (ref($self->{_column})) {
437                 my $trans = $self->{_column}->{transform} || 'Bare';
438                 my $pkg = "OpenILS::Reporter::SQLBuilder::Column::Transform::$trans";
439                 if (UNIVERSAL::can($pkg => 'toSQL')) {
440                         $self->{_transform} = $trans;
441                 } else {
442                         $self->{_transform} = 'GenericTransform';
443                 }
444         } elsif( defined($self->{_column}) ) {
445                 $self->{_transform} = 'Bare';
446         } else {
447                 $self->{_transform} = 'NULL';
448         }
449
450
451         return $self;
452 }
453
454 sub name {
455         my $self = shift;
456         if (ref($self->{_column})) {
457                  return $self->{_column}->{colname};
458         } else {
459                 return $self->{_column};
460         }
461 }
462
463 sub toSQL {
464         my $self = shift;
465         my $type = $self->{_transform};
466         return $self->{_sql} if ($self->{_sql});
467         my $toSQL = "OpenILS::Reporter::SQLBuilder::Column::Transform::${type}::toSQL";
468         return $self->{_sql} = $self->$toSQL;
469 }
470
471 sub is_aggregate {
472         my $self = shift;
473         my $type = $self->{_transform};
474         my $is_agg = "OpenILS::Reporter::SQLBuilder::Column::Transform::${type}::is_aggregate";
475         return $self->$is_agg;
476 }
477
478
479 #-------------------------------------------------------------------------------------------------
480 package OpenILS::Reporter::SQLBuilder::Column::OrderBy;
481 use base qw/OpenILS::Reporter::SQLBuilder::Column/;
482
483 sub new {
484         my $class = shift;
485         my $self = $class->SUPER::new(@_);
486
487         my $col_data = shift;
488         $self->{_direction} = $col_data->{direction} || 'ascending';
489         return $self;
490 }
491
492 sub toSQL {
493         my $self = shift;
494         my $dir = ($self->{_direction} =~ /^d/oi) ? 'DESC' : 'ASC';
495         return $self->{_sql} if ($self->{_sql});
496         return $self->{_sql} = $self->SUPER::toSQL .  " $dir";
497 }
498
499
500 #-------------------------------------------------------------------------------------------------
501 package OpenILS::Reporter::SQLBuilder::Column::Select;
502 use base qw/OpenILS::Reporter::SQLBuilder::Column/;
503
504 sub new {
505         my $class = shift;
506         my $self = $class->SUPER::new(@_);
507
508         my $col_data = shift;
509         $self->{_alias} = $col_data->{alias} || $self->name;
510         return $self;
511 }
512
513 sub toSQL {
514         my $self = shift;
515         return $self->{_sql} if ($self->{_sql});
516         return $self->{_sql} = $self->SUPER::toSQL .  ' AS "' . $self->resolve_param( $self->{_alias} ) . '"';
517 }
518
519
520 #-------------------------------------------------------------------------------------------------
521 package OpenILS::Reporter::SQLBuilder::Column::Transform::GenericTransform;
522
523 sub toSQL {
524         my $self = shift;
525         my $name = $self->name;
526         my $func = $self->{_column}->{transform};
527
528         my @params;
529         @params = @{ $self->resolve_param( $self->{_column}->{params} ) } if ($self->{_column}->{params});
530
531         my $sql = $func . '("' . $self->{_relation} . '"."' . $self->name . '"';
532         $sql .= ",'" . join("','", @params) . "'" if (@params);
533         $sql .= ')';
534
535         return $sql;
536 }
537
538 sub is_aggregate { return $self->{_aggregate} }
539
540 #-------------------------------------------------------------------------------------------------
541 package OpenILS::Reporter::SQLBuilder::Column::Transform::Bare;
542
543 sub toSQL {
544         my $self = shift;
545         return '"' . $self->{_relation} . '"."' . $self->name . '"';
546 }
547
548 sub is_aggregate { return 0 }
549
550 #-------------------------------------------------------------------------------------------------
551 package OpenILS::Reporter::SQLBuilder::Column::Transform::upper;
552
553 sub toSQL {
554         my $self = shift;
555         my $params = $self->resolve_param( $self->{_column}->{params} );
556         my $start = $$params[0];
557         my $len = $$params[1];
558         return 'UPPER("' . $self->{_relation} . '"."' . $self->name . '")';
559 }
560
561 sub is_aggregate { return 0 }
562
563
564 #-------------------------------------------------------------------------------------------------
565 package OpenILS::Reporter::SQLBuilder::Column::Transform::lower;
566
567 sub toSQL {
568         my $self = shift;
569         my $params = $self->resolve_param( $self->{_column}->{params} );
570         my $start = $$params[0];
571         my $len = $$params[1];
572         return 'LOWER("' . $self->{_relation} . '"."' . $self->name . '")';
573 }
574
575 sub is_aggregate { return 0 }
576
577
578 #-------------------------------------------------------------------------------------------------
579 package OpenILS::Reporter::SQLBuilder::Column::Transform::substring;
580
581 sub toSQL {
582         my $self = shift;
583         my $params = $self->resolve_param( $self->{_column}->{params} );
584         my $start = $$params[0];
585         my $len = $$params[1];
586         return 'SUBSTRING("' . $self->{_relation} . '"."' . $self->name . "\",$start,$len)";
587 }
588
589 sub is_aggregate { return 0 }
590
591
592 #-------------------------------------------------------------------------------------------------
593 package OpenILS::Reporter::SQLBuilder::Column::Transform::day_name;
594
595 sub toSQL {
596         my $self = shift;
597         return 'TO_CHAR("' . $self->{_relation} . '"."' . $self->name . '", \'Day\')';
598 }
599
600 sub is_aggregate { return 0 }
601
602
603 #-------------------------------------------------------------------------------------------------
604 package OpenILS::Reporter::SQLBuilder::Column::Transform::month_name;
605
606 sub toSQL {
607         my $self = shift;
608         return 'TO_CHAR("' . $self->{_relation} . '"."' . $self->name . '", \'Month\')';
609 }
610
611 sub is_aggregate { return 0 }
612
613
614 #-------------------------------------------------------------------------------------------------
615 package OpenILS::Reporter::SQLBuilder::Column::Transform::doy;
616
617 sub toSQL {
618         my $self = shift;
619         return 'EXTRACT(DOY FROM "' . $self->{_relation} . '"."' . $self->name . '")';
620 }
621
622 sub is_aggregate { return 0 }
623
624
625 #-------------------------------------------------------------------------------------------------
626 package OpenILS::Reporter::SQLBuilder::Column::Transform::woy;
627
628 sub toSQL {
629         my $self = shift;
630         return 'EXTRACT(WEEK FROM "' . $self->{_relation} . '"."' . $self->name . '")';
631 }
632
633 sub is_aggregate { return 0 }
634
635
636 #-------------------------------------------------------------------------------------------------
637 package OpenILS::Reporter::SQLBuilder::Column::Transform::moy;
638
639 sub toSQL {
640         my $self = shift;
641         return 'EXTRACT(MONTH FROM "' . $self->{_relation} . '"."' . $self->name . '")';
642 }
643
644 sub is_aggregate { return 0 }
645
646
647 #-------------------------------------------------------------------------------------------------
648 package OpenILS::Reporter::SQLBuilder::Column::Transform::qoy;
649
650 sub toSQL {
651         my $self = shift;
652         return 'EXTRACT(QUARTER FROM "' . $self->{_relation} . '"."' . $self->name . '")';
653 }
654
655 sub is_aggregate { return 0 }
656
657
658 #-------------------------------------------------------------------------------------------------
659 package OpenILS::Reporter::SQLBuilder::Column::Transform::dom;
660
661 sub toSQL {
662         my $self = shift;
663         return 'EXTRACT(DAY FROM "' . $self->{_relation} . '"."' . $self->name . '")';
664 }
665
666 sub is_aggregate { return 0 }
667
668
669 #-------------------------------------------------------------------------------------------------
670 package OpenILS::Reporter::SQLBuilder::Column::Transform::dow;
671
672 sub toSQL {
673         my $self = shift;
674         return 'EXTRACT(DOW FROM "' . $self->{_relation} . '"."' . $self->name . '")';
675 }
676
677 sub is_aggregate { return 0 }
678
679
680 #-------------------------------------------------------------------------------------------------
681 package OpenILS::Reporter::SQLBuilder::Column::Transform::year_trunc;
682
683 sub toSQL {
684         my $self = shift;
685         return 'EXTRACT(YEAR FROM "' . $self->{_relation} . '"."' . $self->name . '")';
686 }
687
688 sub is_aggregate { return 0 }
689
690
691 #-------------------------------------------------------------------------------------------------
692 package OpenILS::Reporter::SQLBuilder::Column::Transform::month_trunc;
693
694 sub toSQL {
695         my $self = shift;
696         return 'EXTRACT(YEAR FROM "' . $self->{_relation} . '"."' . $self->name . '")' .
697                 ' || \'-\' || LPAD(EXTRACT(MONTH FROM "' . $self->{_relation} . '"."' . $self->name . '"),2,\'0\')';
698 }
699
700 sub is_aggregate { return 0 }
701
702
703 #-------------------------------------------------------------------------------------------------
704 package OpenILS::Reporter::SQLBuilder::Column::Transform::date_trunc;
705
706 sub toSQL {
707         my $self = shift;
708         return 'DATE("' . $self->{_relation} . '"."' . $self->name . '")';
709 }
710
711 sub is_aggregate { return 0 }
712
713
714 #-------------------------------------------------------------------------------------------------
715 package OpenILS::Reporter::SQLBuilder::Column::Transform::hour_trunc;
716
717 sub toSQL {
718         my $self = shift;
719         return 'DATE_TRUNC("' . $self->{_relation} . '"."' . $self->name . '")';
720 }
721
722 sub is_aggregate { return 0 }
723
724
725 #-------------------------------------------------------------------------------------------------
726 package OpenILS::Reporter::SQLBuilder::Column::Transform::quarter;
727
728 sub toSQL {
729         my $self = shift;
730         return 'EXTRACT(YEAR FROM "' . $self->{_relation} . '"."' . $self->name . '")' .
731                 ' || \'-Q\' || EXTRACT(QUARTER FROM "' . $self->{_relation} . '"."' . $self->name . '")';
732 }
733
734 sub is_aggregate { return 0 }
735
736
737 #-------------------------------------------------------------------------------------------------
738 package OpenILS::Reporter::SQLBuilder::Column::Transform::months_ago;
739
740 sub toSQL {
741         my $self = shift;
742         return 'EXTRACT(MONTH FROM AGE(NOW(),"' . $self->{_relation} . '"."' . $self->name . '"))';
743 }
744
745 sub is_aggregate { return 0 }
746
747
748 #-------------------------------------------------------------------------------------------------
749 package OpenILS::Reporter::SQLBuilder::Column::Transform::hod;
750
751 sub toSQL {
752         my $self = shift;
753         return 'EXTRACT(HOUR FROM "' . $self->{_relation} . '"."' . $self->name . '")';
754 }
755
756 sub is_aggregate { return 0 }
757
758
759 #-------------------------------------------------------------------------------------------------
760 package OpenILS::Reporter::SQLBuilder::Column::Transform::quarters_ago;
761
762 sub toSQL {
763         my $self = shift;
764         return 'EXTRACT(QUARTER FROM AGE(NOW(),"' . $self->{_relation} . '"."' . $self->name . '"))';
765 }
766
767 sub is_aggregate { return 0 }
768
769
770 #-------------------------------------------------------------------------------------------------
771 package OpenILS::Reporter::SQLBuilder::Column::Transform::age;
772
773 sub toSQL {
774         my $self = shift;
775         return 'AGE(NOW(),"' . $self->{_relation} . '"."' . $self->name . '")';
776 }
777
778 sub is_aggregate { return 0 }
779
780
781 #-------------------------------------------------------------------------------------------------
782 package OpenILS::Reporter::SQLBuilder::Column::Transform::first;
783
784 sub toSQL {
785         my $self = shift;
786         return 'FIRST("' . $self->{_relation} . '"."' . $self->name . '")';
787 }
788
789 sub is_aggregate { return 1 }
790
791
792 #-------------------------------------------------------------------------------------------------
793 package OpenILS::Reporter::SQLBuilder::Column::Transform::last;
794
795 sub toSQL {
796         my $self = shift;
797         return 'LAST("' . $self->{_relation} . '"."' . $self->name . '")';
798 }
799
800 sub is_aggregate { return 1 }
801
802
803 #-------------------------------------------------------------------------------------------------
804 package OpenILS::Reporter::SQLBuilder::Column::Transform::min;
805
806 sub toSQL {
807         my $self = shift;
808         return 'MIN("' . $self->{_relation} . '"."' . $self->name . '")';
809 }
810
811 sub is_aggregate { return 1 }
812
813
814 #-------------------------------------------------------------------------------------------------
815 package OpenILS::Reporter::SQLBuilder::Column::Transform::max;
816
817 sub toSQL {
818         my $self = shift;
819         return 'MAX("' . $self->{_relation} . '"."' . $self->name . '")';
820 }
821
822 sub is_aggregate { return 1 }
823
824
825 #-------------------------------------------------------------------------------------------------
826 package OpenILS::Reporter::SQLBuilder::Column::Transform::count;
827
828 sub toSQL {
829         my $self = shift;
830         return 'COUNT("' . $self->{_relation} . '"."' . $self->name . '")';
831 }
832
833 sub is_aggregate { return 1 }
834
835
836 #-------------------------------------------------------------------------------------------------
837 package OpenILS::Reporter::SQLBuilder::Column::Transform::count_distinct;
838
839 sub toSQL {
840         my $self = shift;
841         return 'COUNT(DISTINCT "' . $self->{_relation} . '"."' . $self->name . '")';
842 }
843
844 sub is_aggregate { return 1 }
845
846
847 #-------------------------------------------------------------------------------------------------
848 package OpenILS::Reporter::SQLBuilder::Column::Transform::sum;
849
850 sub toSQL {
851         my $self = shift;
852         return 'SUM("' . $self->{_relation} . '"."' . $self->name . '")';
853 }
854
855 sub is_aggregate { return 1 }
856
857
858 #-------------------------------------------------------------------------------------------------
859 package OpenILS::Reporter::SQLBuilder::Column::Transform::average;
860
861 sub toSQL {
862         my $self = shift;
863         return 'AVG("' . $self->{_relation} . '"."' . $self->name .  '")';
864 }
865
866 sub is_aggregate { return 1 }
867
868
869 #-------------------------------------------------------------------------------------------------
870 package OpenILS::Reporter::SQLBuilder::Column::Where;
871 use base qw/OpenILS::Reporter::SQLBuilder::Column/;
872
873 sub new {
874         my $class = shift;
875         my $self = $class->SUPER::new(@_);
876
877         my $col_data = shift;
878         $self->{_condition} = $col_data->{condition};
879
880         return $self;
881 }
882
883 sub _flesh_conditions {
884         my $cond = shift;
885         my $builder = shift;
886         $cond = [$cond] unless (ref($cond) eq 'ARRAY');
887
888         my @out;
889         for my $c (@$cond) {
890                 push @out, OpenILS::Reporter::SQLBuilder::Input->new( $c )->set_builder( $builder );
891         }
892
893         return \@out;
894 }
895
896 sub toSQL {
897         my $self = shift;
898
899         return $self->{_sql} if ($self->{_sql});
900         my $sql = $self->SUPER::toSQL;
901
902         my ($op) = keys %{ $self->{_condition} };
903         my $val = _flesh_conditions( $self->resolve_param( $self->{_condition}->{$op} ), $self->builder );
904
905         if (lc($op) eq 'in') {
906                 $sql .= " IN (". join(",", map { $_->toSQL } @$val).")";
907         } elsif (lc($op) eq 'not in') {
908                 $sql .= " NOT IN (". join(",", map { $_->toSQL } @$val).")";
909         } elsif (lc($op) eq 'between') {
910                 $sql .= " BETWEEN ". join(" AND ", map { $_->toSQL } @$val);
911         } elsif (lc($op) eq 'not between') {
912                 $sql .= " NOT BETWEEN ". join(" AND ", map { $_->toSQL } @$val);
913         } elsif (lc($op) eq 'like') {
914                 $val = $$val[0] if (ref($val) eq 'ARRAY');
915                 $val =~ s/^'(.*)'$/$1/o;
916                 $val =~ s/%/\\\\%/o;
917                 $val =~ s/_/\\\\_/o;
918                 $sql .= " LIKE '\%$val\%'";
919         } elsif (lc($op) eq 'ilike') {
920                 $val = $$val[0] if (ref($val) eq 'ARRAY');
921                 $val =~ s/^'(.*)'$/$1/o;
922                 $val =~ s/%/\\\\%/o;
923                 $val =~ s/_/\\\\_/o;
924                 $sql .= " ILIKE '\%$val\%'";
925         } else {
926                 $val = $$val[0] if (ref($val) eq 'ARRAY');
927                 $sql .= " $op " . $val->toSQL;
928         }
929
930         return $self->{_sql} = $sql;
931 }
932
933
934 #-------------------------------------------------------------------------------------------------
935 package OpenILS::Reporter::SQLBuilder::Column::Having;
936 use base qw/OpenILS::Reporter::SQLBuilder::Column::Where/;
937
938 #-------------------------------------------------------------------------------------------------
939 package OpenILS::Reporter::SQLBuilder::Relation;
940 use base qw/OpenILS::Reporter::SQLBuilder/;
941
942 sub parse {
943         my $self = shift;
944         $self = $self->SUPER::new if (!ref($self));
945
946         my $rel_data = shift;
947
948         $self->{_table} = $rel_data->{table};
949         $self->{_alias} = $rel_data->{alias} || $self->name;
950         $self->{_join} = [];
951         $self->{_columns} = [];
952
953         if ($rel_data->{join}) {
954                 $self->add_join(
955                         $_ => OpenILS::Reporter::SQLBuilder::Relation->parse( $rel_data->{join}->{$_} ) => $rel_data->{join}->{$_}->{key}
956                 ) for ( keys %{ $rel_data->{join} } );
957         }
958
959         return $self;
960 }
961
962 sub add_column {
963         my $self = shift;
964         my $col = shift;
965         
966         push @{ $self->{_columns} }, $col;
967 }
968
969 sub find_column {
970         my $self = shift;
971         my $col = shift;
972         return (grep { $_->name eq $col} @{ $self->{_columns} })[0];
973 }
974
975 sub add_join {
976         my $self = shift;
977         my $col = shift;
978         my $frel = shift;
979         my $fkey = shift;
980
981         if (ref($col) eq 'OpenILS::Reporter::SQLBuilder::Join') {
982                 push @{ $self->{_join} }, $col;
983         } else {
984                 push @{ $self->{_join} }, OpenILS::Reporter::SQLBuilder::Join->build( $self => $col, $frel => $fkey );
985         }
986
987         return $self;
988 }
989
990 sub is_join {
991         my $self = shift;
992         my $j = shift;
993         $self->{_is_join} = $j if ($j);
994         return $self->{_is_join};
995 }
996
997 sub toSQL {
998         my $self = shift;
999         return $self->{_sql} if ($self->{_sql});
1000
1001         my $sql = $self->{_table} .' AS "'. $self->{_alias} .'"';
1002
1003         if (!$self->is_join) {
1004                 for my $j ( @{ $self->{_join} } ) {
1005                         $sql .= $j->toSQL;
1006                 }
1007         }
1008
1009         return $self->{_sql} = $sql;
1010 }
1011
1012 #-------------------------------------------------------------------------------------------------
1013 package OpenILS::Reporter::SQLBuilder::Join;
1014 use base qw/OpenILS::Reporter::SQLBuilder/;
1015
1016 sub build {
1017         my $self = shift;
1018         $self = $self->SUPER::new if (!ref($self));
1019
1020         $self->{_left_rel} = shift;
1021         ($self->{_left_col}) = split(/-/,shift());
1022
1023         $self->{_right_rel} = shift;
1024         $self->{_right_col} = shift;
1025
1026         $self->{_right_rel}->is_join(1);
1027
1028         return $self;
1029 }
1030
1031 sub toSQL {
1032         my $self = shift;
1033         return $self->{_sql} if ($self->{_sql});
1034
1035         my $sql = "\n\tJOIN " . $self->{_right_rel}->toSQL .
1036                 ' ON ("' . $self->{_left_rel}->{_alias} . '"."' . $self->{_left_col} .
1037                 '" = "' . $self->{_right_rel}->{_alias} . '"."' . $self->{_right_col} . '")';
1038
1039         $sql .= $_->toSQL for (@{ $self->{_right_rel}->{_join} });
1040
1041         return $self->{_sql} = $sql;
1042 }
1043
1044 1;