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