]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Reporter/SQLBuilder.pm
adding LIKE/ILIKE 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) = keys %{ $self->{_column} };
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::substring;
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 'SUBSTRING("' . $self->{_relation} . '"."' . $self->name . "\",$start,$len)";
541 }
542
543 sub is_aggregate { return 0 }
544
545
546 #-------------------------------------------------------------------------------------------------
547 package OpenILS::Reporter::SQLBuilder::Column::Transform::day_name;
548
549 sub toSQL {
550         my $self = shift;
551         return 'TO_CHAR("' . $self->{_relation} . '"."' . $self->name . '", \'Day\')';
552 }
553
554 sub is_aggregate { return 0 }
555
556
557 #-------------------------------------------------------------------------------------------------
558 package OpenILS::Reporter::SQLBuilder::Column::Transform::month_name;
559
560 sub toSQL {
561         my $self = shift;
562         return 'TO_CHAR("' . $self->{_relation} . '"."' . $self->name . '", \'Month\')';
563 }
564
565 sub is_aggregate { return 0 }
566
567
568 #-------------------------------------------------------------------------------------------------
569 package OpenILS::Reporter::SQLBuilder::Column::Transform::doy;
570
571 sub toSQL {
572         my $self = shift;
573         return 'EXTRACT(DOY FROM "' . $self->{_relation} . '"."' . $self->name . '")';
574 }
575
576 sub is_aggregate { return 0 }
577
578
579 #-------------------------------------------------------------------------------------------------
580 package OpenILS::Reporter::SQLBuilder::Column::Transform::woy;
581
582 sub toSQL {
583         my $self = shift;
584         return 'EXTRACT(WEEK FROM "' . $self->{_relation} . '"."' . $self->name . '")';
585 }
586
587 sub is_aggregate { return 0 }
588
589
590 #-------------------------------------------------------------------------------------------------
591 package OpenILS::Reporter::SQLBuilder::Column::Transform::moy;
592
593 sub toSQL {
594         my $self = shift;
595         return 'EXTRACT(MONTH FROM "' . $self->{_relation} . '"."' . $self->name . '")';
596 }
597
598 sub is_aggregate { return 0 }
599
600
601 #-------------------------------------------------------------------------------------------------
602 package OpenILS::Reporter::SQLBuilder::Column::Transform::qoy;
603
604 sub toSQL {
605         my $self = shift;
606         return 'EXTRACT(QUARTER FROM "' . $self->{_relation} . '"."' . $self->name . '")';
607 }
608
609 sub is_aggregate { return 0 }
610
611
612 #-------------------------------------------------------------------------------------------------
613 package OpenILS::Reporter::SQLBuilder::Column::Transform::dom;
614
615 sub toSQL {
616         my $self = shift;
617         return 'EXTRACT(DAY FROM "' . $self->{_relation} . '"."' . $self->name . '")';
618 }
619
620 sub is_aggregate { return 0 }
621
622
623 #-------------------------------------------------------------------------------------------------
624 package OpenILS::Reporter::SQLBuilder::Column::Transform::dow;
625
626 sub toSQL {
627         my $self = shift;
628         return 'EXTRACT(DOW FROM "' . $self->{_relation} . '"."' . $self->name . '")';
629 }
630
631 sub is_aggregate { return 0 }
632
633
634 #-------------------------------------------------------------------------------------------------
635 package OpenILS::Reporter::SQLBuilder::Column::Transform::year_trunc;
636
637 sub toSQL {
638         my $self = shift;
639         return 'EXTRACT(YEAR FROM "' . $self->{_relation} . '"."' . $self->name . '")';
640 }
641
642 sub is_aggregate { return 0 }
643
644
645 #-------------------------------------------------------------------------------------------------
646 package OpenILS::Reporter::SQLBuilder::Column::Transform::month_trunc;
647
648 sub toSQL {
649         my $self = shift;
650         return 'EXTRACT(YEAR FROM "' . $self->{_relation} . '"."' . $self->name . '")' .
651                 ' || \'-\' || LPAD(EXTRACT(MONTH FROM "' . $self->{_relation} . '"."' . $self->name . '"),2,\'0\')';
652 }
653
654 sub is_aggregate { return 0 }
655
656
657 #-------------------------------------------------------------------------------------------------
658 package OpenILS::Reporter::SQLBuilder::Column::Transform::quarter;
659
660 sub toSQL {
661         my $self = shift;
662         return 'EXTRACT(YEAR FROM "' . $self->{_relation} . '"."' . $self->name . '")' .
663                 ' || \'-Q\' || EXTRACT(QUARTER FROM "' . $self->{_relation} . '"."' . $self->name . '")';
664 }
665
666 sub is_aggregate { return 0 }
667
668
669 #-------------------------------------------------------------------------------------------------
670 package OpenILS::Reporter::SQLBuilder::Column::Transform::months_ago;
671
672 sub toSQL {
673         my $self = shift;
674         return 'EXTRACT(MONTH FROM AGE(NOW(),"' . $self->{_relation} . '"."' . $self->name . '"))';
675 }
676
677 sub is_aggregate { return 0 }
678
679
680 #-------------------------------------------------------------------------------------------------
681 package OpenILS::Reporter::SQLBuilder::Column::Transform::quarters_ago;
682
683 sub toSQL {
684         my $self = shift;
685         return 'EXTRACT(QUARTER FROM AGE(NOW(),"' . $self->{_relation} . '"."' . $self->name . '"))';
686 }
687
688 sub is_aggregate { return 0 }
689
690
691 #-------------------------------------------------------------------------------------------------
692 package OpenILS::Reporter::SQLBuilder::Column::Transform::age;
693
694 sub toSQL {
695         my $self = shift;
696         return 'AGE(NOW(),"' . $self->{_relation} . '"."' . $self->name . '")';
697 }
698
699 sub is_aggregate { return 0 }
700
701
702 #-------------------------------------------------------------------------------------------------
703 package OpenILS::Reporter::SQLBuilder::Column::Transform::first;
704
705 sub toSQL {
706         my $self = shift;
707         return 'FIRST("' . $self->{_relation} . '"."' . $self->name . '")';
708 }
709
710 sub is_aggregate { return 1 }
711
712
713 #-------------------------------------------------------------------------------------------------
714 package OpenILS::Reporter::SQLBuilder::Column::Transform::last;
715
716 sub toSQL {
717         my $self = shift;
718         return 'LAST("' . $self->{_relation} . '"."' . $self->name . '")';
719 }
720
721 sub is_aggregate { return 1 }
722
723
724 #-------------------------------------------------------------------------------------------------
725 package OpenILS::Reporter::SQLBuilder::Column::Transform::min;
726
727 sub toSQL {
728         my $self = shift;
729         return 'MIN("' . $self->{_relation} . '"."' . $self->name . '")';
730 }
731
732 sub is_aggregate { return 1 }
733
734
735 #-------------------------------------------------------------------------------------------------
736 package OpenILS::Reporter::SQLBuilder::Column::Transform::max;
737
738 sub toSQL {
739         my $self = shift;
740         return 'MAX("' . $self->{_relation} . '"."' . $self->name . '")';
741 }
742
743 sub is_aggregate { return 1 }
744
745
746 #-------------------------------------------------------------------------------------------------
747 package OpenILS::Reporter::SQLBuilder::Column::Transform::count;
748
749 sub toSQL {
750         my $self = shift;
751         return 'COUNT("' . $self->{_relation} . '"."' . $self->name . '")';
752 }
753
754 sub is_aggregate { return 1 }
755
756
757 #-------------------------------------------------------------------------------------------------
758 package OpenILS::Reporter::SQLBuilder::Column::Transform::count_distinct;
759
760 sub toSQL {
761         my $self = shift;
762         return 'COUNT(DISTINCT "' . $self->{_relation} . '"."' . $self->name . '")';
763 }
764
765 sub is_aggregate { return 1 }
766
767
768 #-------------------------------------------------------------------------------------------------
769 package OpenILS::Reporter::SQLBuilder::Column::Transform::sum;
770
771 sub toSQL {
772         my $self = shift;
773         return 'SUM("' . $self->{_relation} . '"."' . $self->name . '")';
774 }
775
776 sub is_aggregate { return 1 }
777
778
779 #-------------------------------------------------------------------------------------------------
780 package OpenILS::Reporter::SQLBuilder::Column::Transform::average;
781
782 sub toSQL {
783         my $self = shift;
784         return 'AVG("' . $self->{_relation} . '"."' . $self->name .  '")';
785 }
786
787 sub is_aggregate { return 1 }
788
789
790 #-------------------------------------------------------------------------------------------------
791 package OpenILS::Reporter::SQLBuilder::Column::Where;
792 use base qw/OpenILS::Reporter::SQLBuilder::Column/;
793
794 sub new {
795         my $class = shift;
796         my $self = $class->SUPER::new(@_);
797
798         my $col_data = shift;
799         $self->{_condition} = $col_data->{condition};
800
801         return $self;
802 }
803
804 sub _flesh_conditions {
805         my $cond = shift;
806         $cond = [$cond] unless (ref($cond) eq 'ARRAY');
807
808         my @out;
809         for my $c (@$cond) {
810                 push @out, OpenILS::Reporter::SQLBuilder::Input->new( $c );
811         }
812
813         return \@out;
814 }
815
816 sub toSQL {
817         my $self = shift;
818
819         return $self->{_sql} if ($self->{_sql});
820         my $sql = $self->SUPER::toSQL;
821
822         my ($op) = keys %{ $self->{_condition} };
823         my $val = _flesh_conditions( $self->resolve_param( $self->{_condition}->{$op} ) );
824
825         if (lc($op) eq 'in') {
826                 $sql .= " IN (". join(",", map { $_->toSQL } @$val).")";
827         } elsif (lc($op) eq 'not in') {
828                 $sql .= " NOT IN (". join(",", map { $_->toSQL } @$val).")";
829         } elsif (lc($op) eq 'between') {
830                 $sql .= " BETWEEN ". join(" AND ", map { $_->toSQL } @$val);
831         } elsif (lc($op) eq 'not between') {
832                 $sql .= " NOT BETWEEN ". join(" AND ", map { $_->toSQL } @$val);
833         } elsif (lc($op) eq 'like') {
834                 $val = $$val[0] if (ref($val) eq 'ARRAY');
835                 $val =~ s/^'(.*)'$/$1/o;
836                 $val =~ s/%/\\\\%/o;
837                 $val =~ s/_/\\\\_/o;
838                 $sql .= " LIKE '\%$val\%'";
839         } elsif (lc($op) eq 'ilike') {
840                 $val = $$val[0] if (ref($val) eq 'ARRAY');
841                 $val =~ s/^'(.*)'$/$1/o;
842                 $val =~ s/%/\\\\%/o;
843                 $val =~ s/_/\\\\_/o;
844                 $sql .= " ILIKE '\%$val\%'";
845         } else {
846                 $val = $$val[0] if (ref($val) eq 'ARRAY');
847                 $sql .= " $op " . $val->toSQL;
848         }
849
850         return $self->{_sql} = $sql;
851 }
852
853
854 #-------------------------------------------------------------------------------------------------
855 package OpenILS::Reporter::SQLBuilder::Column::Having;
856 use base qw/OpenILS::Reporter::SQLBuilder::Column::Where/;
857
858 #-------------------------------------------------------------------------------------------------
859 package OpenILS::Reporter::SQLBuilder::Relation;
860 use base qw/OpenILS::Reporter::SQLBuilder/;
861
862 sub parse {
863         my $self = shift;
864         $self = $self->SUPER::new if (!ref($self));
865
866         my $rel_data = shift;
867
868         $self->{_table} = $rel_data->{table};
869         $self->{_alias} = $rel_data->{alias} || $self->name;
870         $self->{_join} = [];
871         $self->{_columns} = [];
872
873         if ($rel_data->{join}) {
874                 $self->add_join(
875                         $_ => OpenILS::Reporter::SQLBuilder::Relation->parse( $rel_data->{join}->{$_} ) => $rel_data->{join}->{$_}->{key}
876                 ) for ( keys %{ $rel_data->{join} } );
877         }
878
879         return $self;
880 }
881
882 sub add_column {
883         my $self = shift;
884         my $col = shift;
885         
886         push @{ $self->{_columns} }, $col;
887 }
888
889 sub find_column {
890         my $self = shift;
891         my $col = shift;
892         return (grep { $_->name eq $col} @{ $self->{_columns} })[0];
893 }
894
895 sub add_join {
896         my $self = shift;
897         my $col = shift;
898         my $frel = shift;
899         my $fkey = shift;
900
901         if (ref($col) eq 'OpenILS::Reporter::SQLBuilder::Join') {
902                 push @{ $self->{_join} }, $col;
903         } else {
904                 push @{ $self->{_join} }, OpenILS::Reporter::SQLBuilder::Join->build( $self => $col, $frel => $fkey );
905         }
906
907         return $self;
908 }
909
910 sub is_join {
911         my $self = shift;
912         my $j = shift;
913         $self->{_is_join} = $j if ($j);
914         return $self->{_is_join};
915 }
916
917 sub toSQL {
918         my $self = shift;
919         return $self->{_sql} if ($self->{_sql});
920
921         my $sql = $self->{_table} .' AS "'. $self->{_alias} .'"';
922
923         if (!$self->is_join) {
924                 for my $j ( @{ $self->{_join} } ) {
925                         $sql .= $j->toSQL;
926                 }
927         }
928
929         return $self->{_sql} = $sql;
930 }
931
932 #-------------------------------------------------------------------------------------------------
933 package OpenILS::Reporter::SQLBuilder::Join;
934 use base qw/OpenILS::Reporter::SQLBuilder/;
935
936 sub build {
937         my $self = shift;
938         $self = $self->SUPER::new if (!ref($self));
939
940         $self->{_left_rel} = shift;
941         ($self->{_left_col}) = split(/-/,shift());
942
943         $self->{_right_rel} = shift;
944         $self->{_right_col} = shift;
945
946         $self->{_right_rel}->is_join(1);
947
948         return $self;
949 }
950
951 sub toSQL {
952         my $self = shift;
953         return $self->{_sql} if ($self->{_sql});
954
955         my $sql = "\n\tJOIN " . $self->{_right_rel}->toSQL .
956                 ' ON ("' . $self->{_left_rel}->{_alias} . '"."' . $self->{_left_col} .
957                 '" = "' . $self->{_right_rel}->{_alias} . '"."' . $self->{_right_col} . '")';
958
959         $sql .= $_->toSQL for (@{ $self->{_right_rel}->{_join} });
960
961         return $self->{_sql} = $sql;
962 }
963
964 1;