1 #-------------------------------------------------------------------------------------------------
2 package OpenILS::Reporter::SQLBuilder;
6 $class = ref($class) || $class;
8 return bless { _sql => undef } => $class;
14 $self->{_params} = $p;
20 return $self->{_builder}->{_params}->{$p};
25 $self->{_builder} = shift;
31 return $self->{_builder};
37 $self->builder->{_relative_time} = $t if (defined $t);
38 return $self->builder->{_relative_time};
44 $self->builder->{_resultset_limit} = $limit if (defined $limit);
45 return $self->builder->{_resultset_limit};
52 if (defined($val) && $val =~ /^::(.+)$/o) {
53 $val = $self->get_param($1);
56 if (defined($val) && !ref($val)) {
68 my $rs = OpenILS::Reporter::SQLBuilder::ResultSet->new;
70 if (!$report->{order_by} || @{$report->{order_by}} == 0) {
71 $report->{order_by} = $report->{select};
74 $rs->is_subquery( 1 ) if ( $report->{alias} );
76 $rs ->set_builder( $self )
77 ->set_subquery_alias( $report->{alias} )
78 ->set_select( $report->{select} )
79 ->set_from( $report->{from} )
80 ->set_where( $report->{where} )
81 ->set_having( $report->{having} )
82 ->set_order_by( $report->{order_by} )
83 ->set_do_rollup( $report->{do_rollup} )
84 ->set_pivot_data( $report->{pivot_data} )
85 ->set_pivot_label( $report->{pivot_label} )
86 ->set_pivot_default( $report->{pivot_default} );
92 #-------------------------------------------------------------------------------------------------
93 package OpenILS::Reporter::SQLBuilder::ResultSet;
94 use base qw/OpenILS::Reporter::SQLBuilder/;
99 $self->{_is_subquery} = $flag if (defined $flag);
100 return $self->{_is_subquery};
105 return $self->builder->{_do_rollup};
110 return $self->builder->{_pivot_data};
115 return $self->builder->{_pivot_label};
120 return $self->builder->{_pivot_default};
126 $self->builder->{_do_rollup} = $p if (defined $p);
130 sub set_pivot_default {
133 $self->builder->{_pivot_default} = $p if (defined $p);
140 $self->builder->{_pivot_data} = $p if (defined $p);
144 sub set_pivot_label {
147 $self->builder->{_pivot_label} = $p if (defined $p);
151 sub set_subquery_alias {
154 $self->{_alias} = $alias if (defined $alias);
162 $self->{_select} = [];
164 return $self unless (@cols && defined($cols[0]));
165 @cols = @{ $cols[0] } if (@cols == 1 && ref($cols[0]) eq 'ARRAY');
167 push @{ $self->{_select} }, map { OpenILS::Reporter::SQLBuilder::Column::Select->new( $_ )->set_builder( $self->builder ) } @cols;
176 $self->{_from} = OpenILS::Reporter::SQLBuilder::Relation->parse( $f, $self->builder );
185 $self->{_where} = [];
187 return $self unless (@cols && defined($cols[0]));
188 @cols = @{ $cols[0] } if (@cols == 1 && ref($cols[0]) eq 'ARRAY');
190 push @{ $self->{_where} }, map { OpenILS::Reporter::SQLBuilder::Column::Where->new( $_ )->set_builder( $self->builder ) } @cols;
199 $self->{_having} = [];
201 return $self unless (@cols && defined($cols[0]));
202 @cols = @{ $cols[0] } if (@cols == 1 && ref($cols[0]) eq 'ARRAY');
204 push @{ $self->{_having} }, map { OpenILS::Reporter::SQLBuilder::Column::Having->new( $_ )->set_builder( $self->builder ) } @cols;
213 $self->{_order_by} = [];
215 return $self unless (@cols && defined($cols[0]));
216 @cols = @{ $cols[0] } if (@cols == 1 && ref($cols[0]) eq 'ARRAY');
218 push @{ $self->{_order_by} }, map { OpenILS::Reporter::SQLBuilder::Column::OrderBy->new( $_ )->set_builder( $self->builder ) } @cols;
223 sub column_label_list {
227 push @labels, $self->resolve_param( $_->{_alias} ) for ( @{ $self->{_select} } );
234 $base = 1 unless (defined $base);
239 for my $c ( @{ $self->{_select} } ) {
240 if ($base == 0 && !$seen_label && defined($self->pivot_label) && $gcount == $self->pivot_label - 1) {
244 push @group_by, $gcount if (!$c->is_aggregate);
254 return $self->{_sql} if ($self->{_sql});
258 if ($self->is_subquery) {
260 } elsif ($self->resultset_limit) {
261 $sql = 'SELECT * FROM (';
264 $sql .= "SELECT\t" . join(",\n\t", map { $_->toSQL } @{ $self->{_select} }) . "\n" if (@{ $self->{_select} });
265 $sql .= " FROM\t" . $self->{_from}->toSQL . "\n" if ($self->{_from});
266 $sql .= " WHERE\t" . join("\n\tAND ", map { $_->toSQL } @{ $self->{_where} }) . "\n" if (@{ $self->{_where} });
268 my @group_by = $self->group_by_list;
270 # The GROUP BY clause is used to generate distinct rows even if there are no aggregates in the select list
271 my $rollup_start = 'ROLLUP (';
272 my $rollup_end = ')';
274 $rollup_start = $rollup_end = ''
275 if (!$self->do_rollup or scalar(@group_by) == scalar(@{$self->{_select}})); # No ROLLUP if there are no aggregates, or not requested
277 $sql .= " GROUP BY $rollup_start" . join(', ', @group_by) . "$rollup_end\n" if (@group_by);
278 $sql .= " HAVING " . join("\n\tAND ", map { $_->toSQL } @{ $self->{_having} }) . "\n" if (@{ $self->{_having} });
279 $sql .= ' ORDER BY ' . join(', ', map { $_->toSQL } @{ $self->{_order_by} }) . "\n" if (@{ $self->{_order_by} });
281 if ($self->is_subquery) {
282 $sql .= ') '. $self->{_alias} . "\n";
283 } elsif ($self->resultset_limit) {
284 $sql .= ') limited_to_' . $self->resultset_limit .
285 '_hits LIMIT ' . $self->resultset_limit . "\n";
288 return $self->{_sql} = $sql;
292 #-------------------------------------------------------------------------------------------------
293 package OpenILS::Reporter::SQLBuilder::Input;
294 use base qw/OpenILS::Reporter::SQLBuilder/;
298 my $self = $class->SUPER::new;
300 my $col_data = shift;
302 if (ref($col_data)) {
303 $self->{params} = $col_data->{params};
304 my $trans = $col_data->{transform} || 'Bare';
305 my $pkg = "OpenILS::Reporter::SQLBuilder::Input::Transform::$trans";
306 if (UNIVERSAL::can($pkg => 'toSQL')) {
307 $self->{_transform} = $trans;
309 $self->{_transform} = 'GenericTransform';
311 } elsif( defined($col_data) ) {
312 $self->{_transform} = 'Bare';
313 $self->{params} = $col_data;
315 $self->{_transform} = 'NULL';
325 my $type = $self->{_transform};
326 return $self->{_sql} if ($self->{_sql});
327 my $toSQL = "OpenILS::Reporter::SQLBuilder::Input::Transform::${type}::toSQL";
328 return $self->{_sql} = $self->$toSQL;
331 #-------------------------------------------------------------------------------------------------
332 package OpenILS::Reporter::SQLBuilder::Input::Transform::GenericTransform;
336 my $func = $self->{transform};
339 @params = @{ $self->{params} } if ($self->{params});
341 my $sql = $func . "(\$_$$\$";
342 $sql .= join("\$_$$\$,\$_$$\$", @params) if (@params);
349 #-------------------------------------------------------------------------------------------------
350 package OpenILS::Reporter::SQLBuilder::Input::Transform::NULL;
357 #-------------------------------------------------------------------------------------------------
358 package OpenILS::Reporter::SQLBuilder::Input::Transform::Bare;
363 my $val = $self->{params};
364 $val = $$val[0] if (ref($val));
366 return "\$_$$\$$val\$_$$\$";
370 #-------------------------------------------------------------------------------------------------
371 package OpenILS::Reporter::SQLBuilder::Input::Transform::age;
376 my $val = $self->{params};
377 $val = $$val[0] if (ref($val));
379 return "AGE(NOW(),\$_$$\$$val\$_$$\$::TIMESTAMPTZ)";
382 sub is_aggregate { return 0 }
385 #-------------------------------------------------------------------------------------------------
386 package OpenILS::Reporter::SQLBuilder::Input::Transform::relative_year;
391 my $rtime = $self->relative_time || 'now';
392 my $val = $self->{params};
393 $val = $$val[0] if (ref($val));
395 return "EXTRACT(YEAR FROM \$_$$\$$rtime\$_$$\$::TIMESTAMPTZ + \$_$$\$$val years\$_$$\$)";
399 #-------------------------------------------------------------------------------------------------
400 package OpenILS::Reporter::SQLBuilder::Input::Transform::relative_month;
405 my $rtime = $self->relative_time || 'now';
406 my $val = $self->{params};
407 $val = $$val[0] if (ref($val));
409 return "(EXTRACT(YEAR FROM \$_$$\$$rtime\$_$$\$::TIMESTAMPTZ + \$_$$\$$val months\$_$$\$)" .
410 " || \$_$$\$-\$_$$\$ || LPAD(EXTRACT(MONTH FROM \$_$$\$$rtime\$_$$\$::TIMESTAMPTZ + \$_$$\$$val months\$_$$\$)::text,2,\$_$$\$0\$_$$\$))";
414 #-------------------------------------------------------------------------------------------------
415 package OpenILS::Reporter::SQLBuilder::Input::Transform::relative_date;
420 my $rtime = $self->relative_time || 'now';
421 my $val = $self->{params};
422 $val = $$val[0] if (ref($val));
424 return "DATE(\$_$$\$$rtime\$_$$\$::TIMESTAMPTZ + \$_$$\$$val days\$_$$\$)";
428 #-------------------------------------------------------------------------------------------------
429 package OpenILS::Reporter::SQLBuilder::Input::Transform::relative_week;
434 my $rtime = $self->relative_time || 'now';
435 my $val = $self->{params};
436 $val = $$val[0] if (ref($val));
438 return "EXTRACT(WEEK FROM \$_$$\$rtime\$_$$\$::TIMESTAMPTZ + \$_$$\$$val weeks\$_$$\$)";
442 #-------------------------------------------------------------------------------------------------
443 package OpenILS::Reporter::SQLBuilder::Column;
444 use base qw/OpenILS::Reporter::SQLBuilder/;
448 my $self = $class->SUPER::new;
450 my $col_data = shift;
451 $self->{_relation} = $col_data->{relation};
452 $self->{_column} = $col_data->{column};
454 $self->{_aggregate} = $col_data->{aggregate};
456 if (ref($self->{_column})) {
457 my $trans = $self->{_column}->{transform} || 'Bare';
458 my $pkg = "OpenILS::Reporter::SQLBuilder::Column::Transform::$trans";
459 if (UNIVERSAL::can($pkg => 'toSQL')) {
460 $self->{_transform} = $trans;
462 $self->{_transform} = 'GenericTransform';
464 } elsif( defined($self->{_column}) ) {
465 $self->{_transform} = 'Bare';
467 $self->{_transform} = 'NULL';
476 return $self->builder->{_rels}->{$self->{_relation}};
481 if (ref($self->{_column})) {
482 return $self->{_column}->{colname};
484 return $self->{_column};
490 my $type = $self->{_transform};
491 return $self->{_sql} if ($self->{_sql});
492 my $toSQL = "OpenILS::Reporter::SQLBuilder::Column::Transform::${type}::toSQL";
493 return $self->{_sql} = $self->$toSQL;
498 my $type = $self->{_transform};
499 my $is_agg = "OpenILS::Reporter::SQLBuilder::Column::Transform::${type}::is_aggregate";
500 return $self->$is_agg;
504 #-------------------------------------------------------------------------------------------------
505 package OpenILS::Reporter::SQLBuilder::Column::OrderBy;
506 use base qw/OpenILS::Reporter::SQLBuilder::Column/;
510 my $self = $class->SUPER::new(@_);
512 my $col_data = shift;
513 $self->{_direction} = $col_data->{direction} || 'ascending';
519 my $dir = ($self->{_direction} =~ /^d/oi) ? 'DESC' : 'ASC';
520 return $self->{_sql} if ($self->{_sql});
521 return $self->{_sql} = $self->SUPER::toSQL . " $dir";
525 #-------------------------------------------------------------------------------------------------
526 package OpenILS::Reporter::SQLBuilder::Column::Select;
527 use base qw/OpenILS::Reporter::SQLBuilder::Column/;
531 my $self = $class->SUPER::new(@_);
533 my $col_data = shift;
534 $self->{_alias} = $col_data->{alias} || $self->name;
540 return $self->{_sql} if ($self->{_sql});
541 return $self->{_sql} = $self->SUPER::toSQL . ' AS "' . $self->resolve_param( $self->{_alias} ) . '"';
545 #-------------------------------------------------------------------------------------------------
546 package OpenILS::Reporter::SQLBuilder::Column::Transform::GenericTransform;
550 my $name = $self->name;
551 my $func = $self->{_column}->{transform};
554 @params = @{ $self->resolve_param( $self->{_column}->{params} ) } if ($self->{_column}->{params});
556 my $sql = $func . '("' . $self->{_relation} . '"."' . $self->name . '"';
557 $sql .= ",\$_$$\$" . join("\$_$$\$,\$_$$\$", @params) . "\$_$$\$" if (@params);
563 sub is_aggregate { return $self->{_aggregate} }
565 #-------------------------------------------------------------------------------------------------
566 package OpenILS::Reporter::SQLBuilder::Column::Transform::Bare;
570 return '"' . $self->{_relation} . '"."' . $self->name . '"';
573 sub is_aggregate { return 0 }
575 #-------------------------------------------------------------------------------------------------
576 package OpenILS::Reporter::SQLBuilder::Column::Transform::upper;
580 my $params = $self->resolve_param( $self->{_column}->{params} );
581 return 'UPPER("' . $self->{_relation} . '"."' . $self->name . '")';
584 sub is_aggregate { return 0 }
587 #-------------------------------------------------------------------------------------------------
588 package OpenILS::Reporter::SQLBuilder::Column::Transform::lower;
592 my $params = $self->resolve_param( $self->{_column}->{params} );
593 return 'evergreen.lowercase("' . $self->{_relation} . '"."' . $self->name . '")';
596 sub is_aggregate { return 0 }
599 #-------------------------------------------------------------------------------------------------
600 package OpenILS::Reporter::SQLBuilder::Column::Transform::substring;
604 my $params = $self->resolve_param( $self->{_column}->{params} );
605 my $start = $$params[0];
606 my $len = $$params[1];
607 return 'SUBSTRING("' . $self->{_relation} . '"."' . $self->name . "\",$start,$len)";
610 sub is_aggregate { return 0 }
613 #-------------------------------------------------------------------------------------------------
614 package OpenILS::Reporter::SQLBuilder::Column::Transform::day_name;
618 return 'TO_CHAR("' . $self->{_relation} . '"."' . $self->name . '", \'Day\')';
621 sub is_aggregate { return 0 }
624 #-------------------------------------------------------------------------------------------------
625 package OpenILS::Reporter::SQLBuilder::Column::Transform::month_name;
629 return 'TO_CHAR("' . $self->{_relation} . '"."' . $self->name . '", \'Month\')';
632 sub is_aggregate { return 0 }
635 #-------------------------------------------------------------------------------------------------
636 package OpenILS::Reporter::SQLBuilder::Column::Transform::doy;
640 return 'EXTRACT(DOY FROM "' . $self->{_relation} . '"."' . $self->name . '")';
643 sub is_aggregate { return 0 }
646 #-------------------------------------------------------------------------------------------------
647 package OpenILS::Reporter::SQLBuilder::Column::Transform::woy;
651 return 'EXTRACT(WEEK FROM "' . $self->{_relation} . '"."' . $self->name . '")';
654 sub is_aggregate { return 0 }
657 #-------------------------------------------------------------------------------------------------
658 package OpenILS::Reporter::SQLBuilder::Column::Transform::moy;
662 return 'EXTRACT(MONTH FROM "' . $self->{_relation} . '"."' . $self->name . '")';
665 sub is_aggregate { return 0 }
668 #-------------------------------------------------------------------------------------------------
669 package OpenILS::Reporter::SQLBuilder::Column::Transform::qoy;
673 return 'EXTRACT(QUARTER FROM "' . $self->{_relation} . '"."' . $self->name . '")';
676 sub is_aggregate { return 0 }
679 #-------------------------------------------------------------------------------------------------
680 package OpenILS::Reporter::SQLBuilder::Column::Transform::dom;
684 return 'EXTRACT(DAY FROM "' . $self->{_relation} . '"."' . $self->name . '")';
687 sub is_aggregate { return 0 }
690 #-------------------------------------------------------------------------------------------------
691 package OpenILS::Reporter::SQLBuilder::Column::Transform::dow;
695 return 'EXTRACT(DOW FROM "' . $self->{_relation} . '"."' . $self->name . '")';
698 sub is_aggregate { return 0 }
701 #-------------------------------------------------------------------------------------------------
702 package OpenILS::Reporter::SQLBuilder::Column::Transform::year_trunc;
706 return 'EXTRACT(YEAR FROM "' . $self->{_relation} . '"."' . $self->name . '")';
709 sub is_aggregate { return 0 }
712 #-------------------------------------------------------------------------------------------------
713 package OpenILS::Reporter::SQLBuilder::Column::Transform::month_trunc;
717 return '(EXTRACT(YEAR FROM "' . $self->{_relation} . '"."' . $self->name . '")' .
718 ' || \'-\' || LPAD(EXTRACT(MONTH FROM "' . $self->{_relation} . '"."' . $self->name . '")::text,2,\'0\'))';
721 sub is_aggregate { return 0 }
724 #-------------------------------------------------------------------------------------------------
725 package OpenILS::Reporter::SQLBuilder::Column::Transform::date_trunc;
729 return 'DATE("' . $self->{_relation} . '"."' . $self->name . '")';
732 sub is_aggregate { return 0 }
735 #-------------------------------------------------------------------------------------------------
736 package OpenILS::Reporter::SQLBuilder::Column::Transform::hour_trunc;
740 return 'EXTRACT(HOUR FROM "' . $self->{_relation} . '"."' . $self->name . '")';
743 sub is_aggregate { return 0 }
746 #-------------------------------------------------------------------------------------------------
747 package OpenILS::Reporter::SQLBuilder::Column::Transform::quarter;
751 return '(EXTRACT(YEAR FROM "' . $self->{_relation} . '"."' . $self->name . '")' .
752 ' || \'-Q\' || EXTRACT(QUARTER FROM "' . $self->{_relation} . '"."' . $self->name . '"))';
755 sub is_aggregate { return 0 }
758 #-------------------------------------------------------------------------------------------------
759 package OpenILS::Reporter::SQLBuilder::Column::Transform::months_ago;
763 return '(EXTRACT(YEAR FROM AGE(NOW(),"' . $self->{_relation} . '"."' . $self->name . '")) * 12) +'.
764 ' EXTRACT(MONTH FROM AGE(NOW(),"' . $self->{_relation} . '"."' . $self->name . '"))';
767 sub is_aggregate { return 0 }
770 #-------------------------------------------------------------------------------------------------
771 package OpenILS::Reporter::SQLBuilder::Column::Transform::hod;
775 return 'EXTRACT(HOUR FROM "' . $self->{_relation} . '"."' . $self->name . '")';
778 sub is_aggregate { return 0 }
781 #-------------------------------------------------------------------------------------------------
782 package OpenILS::Reporter::SQLBuilder::Column::Transform::quarters_ago;
786 return '(EXTRACT(YEAR FROM AGE(NOW(),"' . $self->{_relation} . '"."' . $self->name . '")) * 4) +'.
787 ' EXTRACT(QUARTER FROM AGE(NOW(),"' . $self->{_relation} . '"."' . $self->name . '"))';
790 sub is_aggregate { return 0 }
793 #-------------------------------------------------------------------------------------------------
794 package OpenILS::Reporter::SQLBuilder::Column::Transform::age;
798 return 'AGE(NOW(),"' . $self->{_relation} . '"."' . $self->name . '")';
801 sub is_aggregate { return 0 }
804 #-------------------------------------------------------------------------------------------------
805 package OpenILS::Reporter::SQLBuilder::Column::Transform::first;
809 return 'FIRST("' . $self->{_relation} . '"."' . $self->name . '")';
812 sub is_aggregate { return 1 }
815 #-------------------------------------------------------------------------------------------------
816 package OpenILS::Reporter::SQLBuilder::Column::Transform::last;
820 return 'LAST("' . $self->{_relation} . '"."' . $self->name . '")';
823 sub is_aggregate { return 1 }
826 #-------------------------------------------------------------------------------------------------
827 package OpenILS::Reporter::SQLBuilder::Column::Transform::min;
831 return 'MIN("' . $self->{_relation} . '"."' . $self->name . '")';
834 sub is_aggregate { return 1 }
837 #-------------------------------------------------------------------------------------------------
838 package OpenILS::Reporter::SQLBuilder::Column::Transform::max;
842 return 'MAX("' . $self->{_relation} . '"."' . $self->name . '")';
845 sub is_aggregate { return 1 }
848 #-------------------------------------------------------------------------------------------------
849 package OpenILS::Reporter::SQLBuilder::Column::Transform::count;
853 return 'COUNT("' . $self->{_relation} . '"."' . $self->name . '")';
856 sub is_aggregate { return 1 }
859 #-------------------------------------------------------------------------------------------------
860 package OpenILS::Reporter::SQLBuilder::Column::Transform::count_distinct;
864 return 'COUNT(DISTINCT "' . $self->{_relation} . '"."' . $self->name . '")';
867 sub is_aggregate { return 1 }
870 #-------------------------------------------------------------------------------------------------
871 package OpenILS::Reporter::SQLBuilder::Column::Transform::sum;
875 return 'SUM("' . $self->{_relation} . '"."' . $self->name . '")';
878 sub is_aggregate { return 1 }
881 #-------------------------------------------------------------------------------------------------
882 package OpenILS::Reporter::SQLBuilder::Column::Transform::average;
886 return 'AVG("' . $self->{_relation} . '"."' . $self->name . '")';
889 sub is_aggregate { return 1 }
892 #-------------------------------------------------------------------------------------------------
893 package OpenILS::Reporter::SQLBuilder::Column::Where;
894 use base qw/OpenILS::Reporter::SQLBuilder::Column/;
898 my $self = $class->SUPER::new(@_);
900 my $col_data = shift;
901 $self->{_condition} = $col_data->{condition};
906 sub _flesh_conditions {
909 $cond = [$cond] unless (ref($cond) eq 'ARRAY');
913 push @out, OpenILS::Reporter::SQLBuilder::Input->new( $c )->set_builder( $builder );
922 return $self->{_sql} if ($self->{_sql});
926 my $rel = $self->find_relation();
927 if ($rel && $rel->is_nullable) {
928 $sql = "((". $self->SUPER::toSQL .") IS NULL OR ";
931 $sql .= $self->SUPER::toSQL;
933 my ($op) = keys %{ $self->{_condition} };
934 my $val = _flesh_conditions( $self->resolve_param( $self->{_condition}->{$op} ), $self->builder );
936 if (lc($op) eq 'in') {
937 $sql .= " IN (". join(",", map { $_->toSQL } @$val).")";
939 } elsif (lc($op) eq 'not in') {
940 $sql .= " NOT IN (". join(",", map { $_->toSQL } @$val).")";
942 } elsif (lc($op) eq '= any') {
943 $val = $$val[0] if (ref($val) eq 'ARRAY');
945 if ($rel && $rel->is_nullable) { # need to redo this
946 $sql = "((". $self->SUPER::toSQL .") IS NULL OR ";
950 $sql .= "(".$self->SUPER::toSQL.") = ANY ($val)";
952 } elsif (lc($op) eq '<> any') {
953 $val = $$val[0] if (ref($val) eq 'ARRAY');
955 if ($rel && $rel->is_nullable) { # need to redo this
956 $sql = "((". $self->SUPER::toSQL .") IS NULL OR ";
960 $sql .= "(".$self->SUPER::toSQL.") <> ANY ($val)";
962 } elsif (lc($op) eq 'is blank') {
963 if ($rel && $rel->is_nullable) { # need to redo this
964 $sql = "((". $self->SUPER::toSQL .") IS NULL OR ";
968 $sql .= '('. $self->SUPER::toSQL ." IS NULL OR ". $self->SUPER::toSQL ." = '')";
970 } elsif (lc($op) eq 'is not blank') {
971 if ($rel && $rel->is_nullable) { # need to redo this
972 $sql = "((". $self->SUPER::toSQL .") IS NULL OR ";
976 $sql .= '('. $self->SUPER::toSQL ." IS NOT NULL AND ". $self->SUPER::toSQL ." <> '')";
978 } elsif (lc($op) eq 'between') {
979 $sql .= " BETWEEN SYMMETRIC ". join(" AND ", map { $_->toSQL } @$val);
981 } elsif (lc($op) eq 'not between') {
982 $sql .= " NOT BETWEEN SYMMETRIC ". join(" AND ", map { $_->toSQL } @$val);
984 } elsif (lc($op) eq 'like') {
985 $val = $$val[0] if (ref($val) eq 'ARRAY');
987 $val =~ s/\$_$$\$//g;
990 $sql .= " LIKE \$_$$\$\%$val\%\$_$$\$";
992 } elsif (lc($op) eq 'ilike') {
993 $val = $$val[0] if (ref($val) eq 'ARRAY');
995 $val =~ s/\$_$$\$//g;
998 $sql .= " ILIKE \$_$$\$\%$val\%\$_$$\$";
1001 $val = $$val[0] if (ref($val) eq 'ARRAY');
1002 $sql .= " $op " . $val->toSQL;
1005 if ($rel && $rel->is_nullable) {
1009 return $self->{_sql} = $sql;
1013 #-------------------------------------------------------------------------------------------------
1014 package OpenILS::Reporter::SQLBuilder::Column::Having;
1015 use base qw/OpenILS::Reporter::SQLBuilder::Column::Where/;
1017 #-------------------------------------------------------------------------------------------------
1018 package OpenILS::Reporter::SQLBuilder::Relation;
1019 use base qw/OpenILS::Reporter::SQLBuilder/;
1023 $self = $self->SUPER::new if (!ref($self));
1025 my $rel_data = shift;
1027 $self->set_builder($b);
1029 $self->{_table} = $rel_data->{table};
1030 $self->{_alias} = $rel_data->{alias} || $self->{_table};
1031 $self->{_join} = [];
1032 $self->{_columns} = [];
1034 $self->builder->{_rels}{$self->{_alias}} = $self;
1036 if ($rel_data->{join}) {
1038 $_ => OpenILS::Reporter::SQLBuilder::Relation->parse( $rel_data->{join}->{$_}, $b ) => $rel_data->{join}->{$_}->{key} => $rel_data->{join}->{$_}->{type}
1039 ) for ( keys %{ $rel_data->{join} } );
1049 push @{ $self->{_columns} }, $col;
1055 return (grep { $_->name eq $col} @{ $self->{_columns} })[0];
1063 my $type = lc(shift()) || 'inner';
1065 if (UNIVERSAL::isa($col,'OpenILS::Reporter::SQLBuilder::Join')) {
1066 push @{ $self->{_join} }, $col;
1068 push @{ $self->{_join} }, OpenILS::Reporter::SQLBuilder::Join->build( $self => $col, $frel => $fkey, $type );
1076 return $self->{_nullable};
1082 $self->{_is_join} = $j if ($j);
1083 return $self->{_is_join};
1089 $self->{_join_type} = $j if ($j);
1090 return $self->{_join_type};
1095 return $self->{_sql} if ($self->{_sql});
1097 my $sql = $self->{_table} .' AS "'. $self->{_alias} .'"';
1099 if (!$self->is_join) {
1100 for my $j ( @{ $self->{_join} } ) {
1105 return $self->{_sql} = $sql;
1108 #-------------------------------------------------------------------------------------------------
1109 package OpenILS::Reporter::SQLBuilder::Join;
1110 use base qw/OpenILS::Reporter::SQLBuilder/;
1114 my $self = $class->SUPER::new if (!ref($class));
1116 $self->{_left_rel} = shift;
1117 ($self->{_left_col}) = split(/-/,shift());
1119 $self->{_right_rel} = shift;
1120 $self->{_right_col} = shift;
1122 $self->{_join_type} = shift;
1124 $self->{_right_rel}->set_builder($self->{_left_rel}->builder);
1126 $self->{_right_rel}->is_join(1);
1127 $self->{_right_rel}->join_type($self->{_join_type});
1129 bless $self => "OpenILS::Reporter::SQLBuilder::Join::$self->{_join_type}";
1131 if ( $self->{_join_type} eq 'inner' or !$self->{_join_type}) {
1132 $self->{_join_type} = 'i';
1134 if ($self->{_join_type} eq 'left') {
1135 $self->{_right_rel}->{_nullable} = 'l';
1136 } elsif ($self->{_join_type} eq 'right') {
1137 $self->{_left_rel}->{_nullable} = 'r';
1139 $self->{_right_rel}->{_nullable} = 'f';
1140 $self->{_left_rel}->{_nullable} = 'f';
1151 my $sql = "JOIN " . $self->{_right_rel}->toSQL .
1152 ' ON ("' . $self->{_left_rel}->{_alias} . '"."' . $self->{_left_col} .
1153 '" = "' . $self->{_right_rel}->{_alias} . '"."' . $self->{_right_col} . '")';
1155 $sql .= $_->toSQL($dir) for (@{ $self->{_right_rel}->{_join} });
1160 #-------------------------------------------------------------------------------------------------
1161 package OpenILS::Reporter::SQLBuilder::Join::left;
1162 use base qw/OpenILS::Reporter::SQLBuilder::Join/;
1167 #return $self->{_sql} if ($self->{_sql});
1169 my $j = $dir && $dir eq 'r' ? 'FULL OUTER' : 'LEFT OUTER';
1171 my $sql = "\n\t$j ". $self->SUPER::toSQL('l');
1173 #$sql .= $_->toSQL for (@{ $self->{_right_rel}->{_join} });
1175 return $self->{_sql} = $sql;
1178 #-------------------------------------------------------------------------------------------------
1179 package OpenILS::Reporter::SQLBuilder::Join::right;
1180 use base qw/OpenILS::Reporter::SQLBuilder::Join/;
1185 #return $self->{_sql} if ($self->{_sql});
1187 my $_nullable_rel = $dir && $dir eq 'l' ? '_right_rel' : '_left_rel';
1188 $self->{_left_rel}->{_nullable} = 'r';
1189 $self->{$_nullable_rel}->{_nullable} = $dir;
1191 my $j = $dir && $dir eq 'l' ? 'FULL OUTER' : 'RIGHT OUTER';
1193 my $sql = "\n\t$j ". $self->SUPER::toSQL('r');
1195 #$sql .= $_->toSQL for (@{ $self->{_right_rel}->{_join} });
1197 return $self->{_sql} = $sql;
1200 #-------------------------------------------------------------------------------------------------
1201 package OpenILS::Reporter::SQLBuilder::Join::inner;
1202 use base qw/OpenILS::Reporter::SQLBuilder::Join/;
1207 #return $self->{_sql} if ($self->{_sql});
1209 my $_nullable_rel = $dir && $dir eq 'l' ? '_right_rel' : '_left_rel';
1210 $self->{$_nullable_rel}->{_nullable} = $dir;
1214 my $sql = "\n\t$j ". $self->SUPER::toSQL;
1216 #$sql .= $_->toSQL for (@{ $self->{_right_rel}->{_join} });
1218 return $self->{_sql} = $sql;
1221 #-------------------------------------------------------------------------------------------------
1222 package OpenILS::Reporter::SQLBuilder::Join::cross;
1223 use base qw/OpenILS::Reporter::SQLBuilder::Join/;
1227 #return $self->{_sql} if ($self->{_sql});
1229 $self->{_right_rel}->{_nullable} = 'f';
1230 $self->{_left_rel}->{_nullable} = 'f';
1232 my $sql = "\n\tFULL OUTER ". $self->SUPER::toSQL('f');
1234 #$sql .= $_->toSQL for (@{ $self->{_right_rel}->{_join} });
1236 return $self->{_sql} = $sql;