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_pivot_data( $report->{pivot_data} )
84 ->set_pivot_label( $report->{pivot_label} )
85 ->set_pivot_default( $report->{pivot_default} );
91 #-------------------------------------------------------------------------------------------------
92 package OpenILS::Reporter::SQLBuilder::ResultSet;
93 use base qw/OpenILS::Reporter::SQLBuilder/;
98 $self->{_is_subquery} = $flag if (defined $flag);
99 return $self->{_is_subquery};
104 return $self->builder->{_pivot_data};
109 return $self->builder->{_pivot_label};
114 return $self->builder->{_pivot_default};
117 sub set_pivot_default {
120 $self->builder->{_pivot_default} = $p if (defined $p);
127 $self->builder->{_pivot_data} = $p if (defined $p);
131 sub set_pivot_label {
134 $self->builder->{_pivot_label} = $p if (defined $p);
138 sub set_subquery_alias {
141 $self->{_alias} = $alias if (defined $alias);
149 $self->{_select} = [];
151 return $self unless (@cols && defined($cols[0]));
152 @cols = @{ $cols[0] } if (@cols == 1 && ref($cols[0]) eq 'ARRAY');
154 push @{ $self->{_select} }, map { OpenILS::Reporter::SQLBuilder::Column::Select->new( $_ )->set_builder( $self->builder ) } @cols;
163 $self->{_from} = OpenILS::Reporter::SQLBuilder::Relation->parse( $f, $self->builder );
172 $self->{_where} = [];
174 return $self unless (@cols && defined($cols[0]));
175 @cols = @{ $cols[0] } if (@cols == 1 && ref($cols[0]) eq 'ARRAY');
177 push @{ $self->{_where} }, map { OpenILS::Reporter::SQLBuilder::Column::Where->new( $_ )->set_builder( $self->builder ) } @cols;
186 $self->{_having} = [];
188 return $self unless (@cols && defined($cols[0]));
189 @cols = @{ $cols[0] } if (@cols == 1 && ref($cols[0]) eq 'ARRAY');
191 push @{ $self->{_having} }, map { OpenILS::Reporter::SQLBuilder::Column::Having->new( $_ )->set_builder( $self->builder ) } @cols;
200 $self->{_order_by} = [];
202 return $self unless (@cols && defined($cols[0]));
203 @cols = @{ $cols[0] } if (@cols == 1 && ref($cols[0]) eq 'ARRAY');
205 push @{ $self->{_order_by} }, map { OpenILS::Reporter::SQLBuilder::Column::OrderBy->new( $_ )->set_builder( $self->builder ) } @cols;
210 sub column_label_list {
214 push @labels, $self->resolve_param( $_->{_alias} ) for ( @{ $self->{_select} } );
221 $base = 1 unless (defined $base);
226 for my $c ( @{ $self->{_select} } ) {
227 if ($base == 0 && !$seen_label && defined($self->pivot_label) && $gcount == $self->pivot_label - 1) {
231 push @group_by, $gcount if (!$c->is_aggregate);
241 return $self->{_sql} if ($self->{_sql});
245 if ($self->is_subquery) {
247 } elsif ($self->resultset_limit) {
248 $sql = 'SELECT * FROM (';
251 $sql .= "SELECT\t" . join(",\n\t", map { $_->toSQL } @{ $self->{_select} }) . "\n" if (@{ $self->{_select} });
252 $sql .= " FROM\t" . $self->{_from}->toSQL . "\n" if ($self->{_from});
253 $sql .= " WHERE\t" . join("\n\tAND ", map { $_->toSQL } @{ $self->{_where} }) . "\n" if (@{ $self->{_where} });
255 my @group_by = $self->group_by_list;
257 $sql .= ' GROUP BY ' . join(', ', @group_by) . "\n" if (@group_by);
258 $sql .= " HAVING " . join("\n\tAND ", map { $_->toSQL } @{ $self->{_having} }) . "\n" if (@{ $self->{_having} });
259 $sql .= ' ORDER BY ' . join(', ', map { $_->toSQL } @{ $self->{_order_by} }) . "\n" if (@{ $self->{_order_by} });
261 if ($self->is_subquery) {
262 $sql .= ') '. $self->{_alias} . "\n";
263 } elsif ($self->resultset_limit) {
264 $sql .= ') limited_to_' . $self->resultset_limit .
265 '_hits LIMIT ' . $self->resultset_limit . "\n";
268 return $self->{_sql} = $sql;
272 #-------------------------------------------------------------------------------------------------
273 package OpenILS::Reporter::SQLBuilder::Input;
274 use base qw/OpenILS::Reporter::SQLBuilder/;
278 my $self = $class->SUPER::new;
280 my $col_data = shift;
282 if (ref($col_data)) {
283 $self->{params} = $col_data->{params};
284 my $trans = $col_data->{transform} || 'Bare';
285 my $pkg = "OpenILS::Reporter::SQLBuilder::Input::Transform::$trans";
286 if (UNIVERSAL::can($pkg => 'toSQL')) {
287 $self->{_transform} = $trans;
289 $self->{_transform} = 'GenericTransform';
291 } elsif( defined($col_data) ) {
292 $self->{_transform} = 'Bare';
293 $self->{params} = $col_data;
295 $self->{_transform} = 'NULL';
305 my $type = $self->{_transform};
306 return $self->{_sql} if ($self->{_sql});
307 my $toSQL = "OpenILS::Reporter::SQLBuilder::Input::Transform::${type}::toSQL";
308 return $self->{_sql} = $self->$toSQL;
311 #-------------------------------------------------------------------------------------------------
312 package OpenILS::Reporter::SQLBuilder::Input::Transform::GenericTransform;
316 my $func = $self->{transform};
319 @params = @{ $self->{params} } if ($self->{params});
321 my $sql = $func . '(\'';
322 $sql .= join("','", @params) if (@params);
329 #-------------------------------------------------------------------------------------------------
330 package OpenILS::Reporter::SQLBuilder::Input::Transform::NULL;
337 #-------------------------------------------------------------------------------------------------
338 package OpenILS::Reporter::SQLBuilder::Input::Transform::Bare;
343 my $val = $self->{params};
344 $val = $$val[0] if (ref($val));
346 $val =~ s/\\/\\\\/go;
353 #-------------------------------------------------------------------------------------------------
354 package OpenILS::Reporter::SQLBuilder::Input::Transform::age;
359 my $val = $self->{params};
360 $val = $$val[0] if (ref($val));
362 $val =~ s/\\/\\\\/go;
365 return "AGE(NOW(),'" . $val . "'::TIMESTAMPTZ)";
368 sub is_aggregate { return 0 }
371 #-------------------------------------------------------------------------------------------------
372 package OpenILS::Reporter::SQLBuilder::Input::Transform::relative_year;
377 my $rtime = $self->relative_time || 'now';
379 $rtime =~ s/\\/\\\\/go;
380 $rtime =~ s/'/\\'/go;
382 my $val = $self->{params};
383 $val = $$val[0] if (ref($val));
385 $val =~ s/\\/\\\\/go;
388 return "EXTRACT(YEAR FROM '$rtime'::TIMESTAMPTZ + '$val years')";
392 #-------------------------------------------------------------------------------------------------
393 package OpenILS::Reporter::SQLBuilder::Input::Transform::relative_month;
398 my $rtime = $self->relative_time || 'now';
400 $rtime =~ s/\\/\\\\/go;
401 $rtime =~ s/'/\\'/go;
403 my $val = $self->{params};
404 $val = $$val[0] if (ref($val));
406 $val =~ s/\\/\\\\/go;
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';
422 $rtime =~ s/\\/\\\\/go;
423 $rtime =~ s/'/\\'/go;
425 my $val = $self->{params};
426 $val = $$val[0] if (ref($val));
428 $val =~ s/\\/\\\\/go;
431 return "DATE('$rtime'::TIMESTAMPTZ + '$val days')";
435 #-------------------------------------------------------------------------------------------------
436 package OpenILS::Reporter::SQLBuilder::Input::Transform::relative_week;
441 my $rtime = $self->relative_time || 'now';
443 $rtime =~ s/\\/\\\\/go;
444 $rtime =~ s/'/\\'/go;
446 my $val = $self->{params};
447 $val = $$val[0] if (ref($val));
449 $val =~ s/\\/\\\\/go;
452 return "EXTRACT(WEEK FROM '$rtime'::TIMESTAMPTZ + '$val weeks')";
456 #-------------------------------------------------------------------------------------------------
457 package OpenILS::Reporter::SQLBuilder::Column;
458 use base qw/OpenILS::Reporter::SQLBuilder/;
462 my $self = $class->SUPER::new;
464 my $col_data = shift;
465 $self->{_relation} = $col_data->{relation};
466 $self->{_column} = $col_data->{column};
468 $self->{_aggregate} = $col_data->{aggregate};
470 if (ref($self->{_column})) {
471 my $trans = $self->{_column}->{transform} || 'Bare';
472 my $pkg = "OpenILS::Reporter::SQLBuilder::Column::Transform::$trans";
473 if (UNIVERSAL::can($pkg => 'toSQL')) {
474 $self->{_transform} = $trans;
476 $self->{_transform} = 'GenericTransform';
478 } elsif( defined($self->{_column}) ) {
479 $self->{_transform} = 'Bare';
481 $self->{_transform} = 'NULL';
490 return $self->builder->{_rels}->{$self->{_relation}};
495 if (ref($self->{_column})) {
496 return $self->{_column}->{colname};
498 return $self->{_column};
504 my $type = $self->{_transform};
505 return $self->{_sql} if ($self->{_sql});
506 my $toSQL = "OpenILS::Reporter::SQLBuilder::Column::Transform::${type}::toSQL";
507 return $self->{_sql} = $self->$toSQL;
512 my $type = $self->{_transform};
513 my $is_agg = "OpenILS::Reporter::SQLBuilder::Column::Transform::${type}::is_aggregate";
514 return $self->$is_agg;
518 #-------------------------------------------------------------------------------------------------
519 package OpenILS::Reporter::SQLBuilder::Column::OrderBy;
520 use base qw/OpenILS::Reporter::SQLBuilder::Column/;
524 my $self = $class->SUPER::new(@_);
526 my $col_data = shift;
527 $self->{_direction} = $col_data->{direction} || 'ascending';
533 my $dir = ($self->{_direction} =~ /^d/oi) ? 'DESC' : 'ASC';
534 return $self->{_sql} if ($self->{_sql});
535 return $self->{_sql} = $self->SUPER::toSQL . " $dir";
539 #-------------------------------------------------------------------------------------------------
540 package OpenILS::Reporter::SQLBuilder::Column::Select;
541 use base qw/OpenILS::Reporter::SQLBuilder::Column/;
545 my $self = $class->SUPER::new(@_);
547 my $col_data = shift;
548 $self->{_alias} = $col_data->{alias} || $self->name;
554 return $self->{_sql} if ($self->{_sql});
555 return $self->{_sql} = $self->SUPER::toSQL . ' AS "' . $self->resolve_param( $self->{_alias} ) . '"';
559 #-------------------------------------------------------------------------------------------------
560 package OpenILS::Reporter::SQLBuilder::Column::Transform::GenericTransform;
564 my $name = $self->name;
565 my $func = $self->{_column}->{transform};
568 @params = @{ $self->resolve_param( $self->{_column}->{params} ) } if ($self->{_column}->{params});
570 my $sql = $func . '("' . $self->{_relation} . '"."' . $self->name . '"';
571 $sql .= ",'" . join("','", @params) . "'" if (@params);
577 sub is_aggregate { return $self->{_aggregate} }
579 #-------------------------------------------------------------------------------------------------
580 package OpenILS::Reporter::SQLBuilder::Column::Transform::Bare;
584 return '"' . $self->{_relation} . '"."' . $self->name . '"';
587 sub is_aggregate { return 0 }
589 #-------------------------------------------------------------------------------------------------
590 package OpenILS::Reporter::SQLBuilder::Column::Transform::upper;
594 my $params = $self->resolve_param( $self->{_column}->{params} );
595 my $start = $$params[0];
596 my $len = $$params[1];
597 return 'UPPER("' . $self->{_relation} . '"."' . $self->name . '")';
600 sub is_aggregate { return 0 }
603 #-------------------------------------------------------------------------------------------------
604 package OpenILS::Reporter::SQLBuilder::Column::Transform::lower;
608 my $params = $self->resolve_param( $self->{_column}->{params} );
609 my $start = $$params[0];
610 my $len = $$params[1];
611 return 'evergreen.lowercase("' . $self->{_relation} . '"."' . $self->name . '")';
614 sub is_aggregate { return 0 }
617 #-------------------------------------------------------------------------------------------------
618 package OpenILS::Reporter::SQLBuilder::Column::Transform::substring;
622 my $params = $self->resolve_param( $self->{_column}->{params} );
623 my $start = $$params[0];
624 my $len = $$params[1];
625 return 'SUBSTRING("' . $self->{_relation} . '"."' . $self->name . "\",$start,$len)";
628 sub is_aggregate { return 0 }
631 #-------------------------------------------------------------------------------------------------
632 package OpenILS::Reporter::SQLBuilder::Column::Transform::day_name;
636 return 'TO_CHAR("' . $self->{_relation} . '"."' . $self->name . '", \'Day\')';
639 sub is_aggregate { return 0 }
642 #-------------------------------------------------------------------------------------------------
643 package OpenILS::Reporter::SQLBuilder::Column::Transform::month_name;
647 return 'TO_CHAR("' . $self->{_relation} . '"."' . $self->name . '", \'Month\')';
650 sub is_aggregate { return 0 }
653 #-------------------------------------------------------------------------------------------------
654 package OpenILS::Reporter::SQLBuilder::Column::Transform::doy;
658 return 'EXTRACT(DOY FROM "' . $self->{_relation} . '"."' . $self->name . '")';
661 sub is_aggregate { return 0 }
664 #-------------------------------------------------------------------------------------------------
665 package OpenILS::Reporter::SQLBuilder::Column::Transform::woy;
669 return 'EXTRACT(WEEK FROM "' . $self->{_relation} . '"."' . $self->name . '")';
672 sub is_aggregate { return 0 }
675 #-------------------------------------------------------------------------------------------------
676 package OpenILS::Reporter::SQLBuilder::Column::Transform::moy;
680 return 'EXTRACT(MONTH FROM "' . $self->{_relation} . '"."' . $self->name . '")';
683 sub is_aggregate { return 0 }
686 #-------------------------------------------------------------------------------------------------
687 package OpenILS::Reporter::SQLBuilder::Column::Transform::qoy;
691 return 'EXTRACT(QUARTER FROM "' . $self->{_relation} . '"."' . $self->name . '")';
694 sub is_aggregate { return 0 }
697 #-------------------------------------------------------------------------------------------------
698 package OpenILS::Reporter::SQLBuilder::Column::Transform::dom;
702 return 'EXTRACT(DAY FROM "' . $self->{_relation} . '"."' . $self->name . '")';
705 sub is_aggregate { return 0 }
708 #-------------------------------------------------------------------------------------------------
709 package OpenILS::Reporter::SQLBuilder::Column::Transform::dow;
713 return 'EXTRACT(DOW FROM "' . $self->{_relation} . '"."' . $self->name . '")';
716 sub is_aggregate { return 0 }
719 #-------------------------------------------------------------------------------------------------
720 package OpenILS::Reporter::SQLBuilder::Column::Transform::year_trunc;
724 return 'EXTRACT(YEAR FROM "' . $self->{_relation} . '"."' . $self->name . '")';
727 sub is_aggregate { return 0 }
730 #-------------------------------------------------------------------------------------------------
731 package OpenILS::Reporter::SQLBuilder::Column::Transform::month_trunc;
735 return 'EXTRACT(YEAR FROM "' . $self->{_relation} . '"."' . $self->name . '")' .
736 ' || \'-\' || LPAD(EXTRACT(MONTH FROM "' . $self->{_relation} . '"."' . $self->name . '")::text,2,\'0\')';
739 sub is_aggregate { return 0 }
742 #-------------------------------------------------------------------------------------------------
743 package OpenILS::Reporter::SQLBuilder::Column::Transform::date_trunc;
747 return 'DATE("' . $self->{_relation} . '"."' . $self->name . '")';
750 sub is_aggregate { return 0 }
753 #-------------------------------------------------------------------------------------------------
754 package OpenILS::Reporter::SQLBuilder::Column::Transform::hour_trunc;
758 return 'EXTRACT(HOUR FROM "' . $self->{_relation} . '"."' . $self->name . '")';
761 sub is_aggregate { return 0 }
764 #-------------------------------------------------------------------------------------------------
765 package OpenILS::Reporter::SQLBuilder::Column::Transform::quarter;
769 return 'EXTRACT(YEAR FROM "' . $self->{_relation} . '"."' . $self->name . '")' .
770 ' || \'-Q\' || EXTRACT(QUARTER FROM "' . $self->{_relation} . '"."' . $self->name . '")';
773 sub is_aggregate { return 0 }
776 #-------------------------------------------------------------------------------------------------
777 package OpenILS::Reporter::SQLBuilder::Column::Transform::months_ago;
781 return 'EXTRACT(MONTH FROM AGE(NOW(),"' . $self->{_relation} . '"."' . $self->name . '"))';
784 sub is_aggregate { return 0 }
787 #-------------------------------------------------------------------------------------------------
788 package OpenILS::Reporter::SQLBuilder::Column::Transform::hod;
792 return 'EXTRACT(HOUR FROM "' . $self->{_relation} . '"."' . $self->name . '")';
795 sub is_aggregate { return 0 }
798 #-------------------------------------------------------------------------------------------------
799 package OpenILS::Reporter::SQLBuilder::Column::Transform::quarters_ago;
803 return 'EXTRACT(QUARTER FROM AGE(NOW(),"' . $self->{_relation} . '"."' . $self->name . '"))';
806 sub is_aggregate { return 0 }
809 #-------------------------------------------------------------------------------------------------
810 package OpenILS::Reporter::SQLBuilder::Column::Transform::age;
814 return 'AGE(NOW(),"' . $self->{_relation} . '"."' . $self->name . '")';
817 sub is_aggregate { return 0 }
820 #-------------------------------------------------------------------------------------------------
821 package OpenILS::Reporter::SQLBuilder::Column::Transform::first;
825 return 'FIRST("' . $self->{_relation} . '"."' . $self->name . '")';
828 sub is_aggregate { return 1 }
831 #-------------------------------------------------------------------------------------------------
832 package OpenILS::Reporter::SQLBuilder::Column::Transform::last;
836 return 'LAST("' . $self->{_relation} . '"."' . $self->name . '")';
839 sub is_aggregate { return 1 }
842 #-------------------------------------------------------------------------------------------------
843 package OpenILS::Reporter::SQLBuilder::Column::Transform::min;
847 return 'MIN("' . $self->{_relation} . '"."' . $self->name . '")';
850 sub is_aggregate { return 1 }
853 #-------------------------------------------------------------------------------------------------
854 package OpenILS::Reporter::SQLBuilder::Column::Transform::max;
858 return 'MAX("' . $self->{_relation} . '"."' . $self->name . '")';
861 sub is_aggregate { return 1 }
864 #-------------------------------------------------------------------------------------------------
865 package OpenILS::Reporter::SQLBuilder::Column::Transform::count;
869 return 'COUNT("' . $self->{_relation} . '"."' . $self->name . '")';
872 sub is_aggregate { return 1 }
875 #-------------------------------------------------------------------------------------------------
876 package OpenILS::Reporter::SQLBuilder::Column::Transform::count_distinct;
880 return 'COUNT(DISTINCT "' . $self->{_relation} . '"."' . $self->name . '")';
883 sub is_aggregate { return 1 }
886 #-------------------------------------------------------------------------------------------------
887 package OpenILS::Reporter::SQLBuilder::Column::Transform::sum;
891 return 'SUM("' . $self->{_relation} . '"."' . $self->name . '")';
894 sub is_aggregate { return 1 }
897 #-------------------------------------------------------------------------------------------------
898 package OpenILS::Reporter::SQLBuilder::Column::Transform::average;
902 return 'AVG("' . $self->{_relation} . '"."' . $self->name . '")';
905 sub is_aggregate { return 1 }
908 #-------------------------------------------------------------------------------------------------
909 package OpenILS::Reporter::SQLBuilder::Column::Where;
910 use base qw/OpenILS::Reporter::SQLBuilder::Column/;
914 my $self = $class->SUPER::new(@_);
916 my $col_data = shift;
917 $self->{_condition} = $col_data->{condition};
922 sub _flesh_conditions {
925 $cond = [$cond] unless (ref($cond) eq 'ARRAY');
929 push @out, OpenILS::Reporter::SQLBuilder::Input->new( $c )->set_builder( $builder );
938 return $self->{_sql} if ($self->{_sql});
942 my $rel = $self->find_relation();
943 if ($rel && $rel->is_nullable) {
944 $sql = "((". $self->SUPER::toSQL .") IS NULL OR ";
947 $sql .= $self->SUPER::toSQL;
949 my ($op) = keys %{ $self->{_condition} };
950 my $val = _flesh_conditions( $self->resolve_param( $self->{_condition}->{$op} ), $self->builder );
952 if (lc($op) eq 'in') {
953 $sql .= " IN (". join(",", map { $_->toSQL } @$val).")";
955 } elsif (lc($op) eq 'not in') {
956 $sql .= " NOT IN (". join(",", map { $_->toSQL } @$val).")";
958 } elsif (lc($op) eq '= any') {
959 $val = $$val[0] if (ref($val) eq 'ARRAY');
961 if ($rel && $rel->is_nullable) { # need to redo this
962 $sql = "((". $self->SUPER::toSQL .") IS NULL OR ";
966 $sql .= "$val = ANY (".$self->SUPER::toSQL.")";
968 } elsif (lc($op) eq '<> any') {
969 $val = $$val[0] if (ref($val) eq 'ARRAY');
971 if ($rel && $rel->is_nullable) { # need to redo this
972 $sql = "((". $self->SUPER::toSQL .") IS NULL OR ";
976 $sql .= "$val <> ANY (".$self->SUPER::toSQL.")";
978 } elsif (lc($op) eq 'is blank') {
979 if ($rel && $rel->is_nullable) { # need to redo this
980 $sql = "((". $self->SUPER::toSQL .") IS NULL OR ";
984 $sql .= '('. $self->SUPER::toSQL ." IS NULL OR ". $self->SUPER::toSQL ." = '')";
986 } elsif (lc($op) eq 'is not blank') {
987 if ($rel && $rel->is_nullable) { # need to redo this
988 $sql = "((". $self->SUPER::toSQL .") IS NULL OR ";
992 $sql .= '('. $self->SUPER::toSQL ." IS NOT NULL AND ". $self->SUPER::toSQL ." <> '')";
994 } elsif (lc($op) eq 'between') {
995 $sql .= " BETWEEN ". join(" AND ", map { $_->toSQL } @$val);
997 } elsif (lc($op) eq 'not between') {
998 $sql .= " NOT BETWEEN ". join(" AND ", map { $_->toSQL } @$val);
1000 } elsif (lc($op) eq 'like') {
1001 $val = $$val[0] if (ref($val) eq 'ARRAY');
1003 $val =~ s/^'(.*)'$/$1/o;
1004 $val =~ s/%/\\\\%/o;
1005 $val =~ s/_/\\\\_/o;
1006 $sql .= " LIKE '\%$val\%'";
1008 } elsif (lc($op) eq 'ilike') {
1009 $val = $$val[0] if (ref($val) eq 'ARRAY');
1011 $val =~ s/^'(.*)'$/$1/o;
1012 $val =~ s/%/\\\\%/o;
1013 $val =~ s/_/\\\\_/o;
1014 $sql .= " ILIKE '\%$val\%'";
1017 $val = $$val[0] if (ref($val) eq 'ARRAY');
1018 $sql .= " $op " . $val->toSQL;
1021 if ($rel && $rel->is_nullable) {
1025 return $self->{_sql} = $sql;
1029 #-------------------------------------------------------------------------------------------------
1030 package OpenILS::Reporter::SQLBuilder::Column::Having;
1031 use base qw/OpenILS::Reporter::SQLBuilder::Column::Where/;
1033 #-------------------------------------------------------------------------------------------------
1034 package OpenILS::Reporter::SQLBuilder::Relation;
1035 use base qw/OpenILS::Reporter::SQLBuilder/;
1039 $self = $self->SUPER::new if (!ref($self));
1041 my $rel_data = shift;
1043 $self->set_builder($b);
1045 $self->{_table} = $rel_data->{table};
1046 $self->{_alias} = $rel_data->{alias} || $self->{_table};
1047 $self->{_join} = [];
1048 $self->{_columns} = [];
1050 $self->builder->{_rels}{$self->{_alias}} = $self;
1052 if ($rel_data->{join}) {
1054 $_ => OpenILS::Reporter::SQLBuilder::Relation->parse( $rel_data->{join}->{$_}, $b ) => $rel_data->{join}->{$_}->{key} => $rel_data->{join}->{$_}->{type}
1055 ) for ( keys %{ $rel_data->{join} } );
1065 push @{ $self->{_columns} }, $col;
1071 return (grep { $_->name eq $col} @{ $self->{_columns} })[0];
1079 my $type = lc(shift()) || 'inner';
1081 if (UNIVERSAL::isa($col,'OpenILS::Reporter::SQLBuilder::Join')) {
1082 push @{ $self->{_join} }, $col;
1084 push @{ $self->{_join} }, OpenILS::Reporter::SQLBuilder::Join->build( $self => $col, $frel => $fkey, $type );
1092 return $self->{_nullable};
1098 $self->{_is_join} = $j if ($j);
1099 return $self->{_is_join};
1105 $self->{_join_type} = $j if ($j);
1106 return $self->{_join_type};
1111 return $self->{_sql} if ($self->{_sql});
1113 my $sql = $self->{_table} .' AS "'. $self->{_alias} .'"';
1115 if (!$self->is_join) {
1116 for my $j ( @{ $self->{_join} } ) {
1121 return $self->{_sql} = $sql;
1124 #-------------------------------------------------------------------------------------------------
1125 package OpenILS::Reporter::SQLBuilder::Join;
1126 use base qw/OpenILS::Reporter::SQLBuilder/;
1130 my $self = $class->SUPER::new if (!ref($class));
1132 $self->{_left_rel} = shift;
1133 ($self->{_left_col}) = split(/-/,shift());
1135 $self->{_right_rel} = shift;
1136 $self->{_right_col} = shift;
1138 $self->{_join_type} = shift;
1140 $self->{_right_rel}->set_builder($self->{_left_rel}->builder);
1142 $self->{_right_rel}->is_join(1);
1143 $self->{_right_rel}->join_type($self->{_join_type});
1145 bless $self => "OpenILS::Reporter::SQLBuilder::Join::$self->{_join_type}";
1147 if ( $self->{_join_type} eq 'inner' or !$self->{_join_type}) {
1148 $self->{_join_type} = 'i';
1150 if ($self->{_join_type} eq 'left') {
1151 $self->{_right_rel}->{_nullable} = 'l';
1152 } elsif ($self->{_join_type} eq 'right') {
1153 $self->{_left_rel}->{_nullable} = 'r';
1155 $self->{_right_rel}->{_nullable} = 'f';
1156 $self->{_left_rel}->{_nullable} = 'f';
1167 my $sql = "JOIN " . $self->{_right_rel}->toSQL .
1168 ' ON ("' . $self->{_left_rel}->{_alias} . '"."' . $self->{_left_col} .
1169 '" = "' . $self->{_right_rel}->{_alias} . '"."' . $self->{_right_col} . '")';
1171 $sql .= $_->toSQL($dir) for (@{ $self->{_right_rel}->{_join} });
1176 #-------------------------------------------------------------------------------------------------
1177 package OpenILS::Reporter::SQLBuilder::Join::left;
1178 use base qw/OpenILS::Reporter::SQLBuilder::Join/;
1183 #return $self->{_sql} if ($self->{_sql});
1185 my $j = $dir && $dir eq 'r' ? 'FULL OUTER' : 'LEFT OUTER';
1187 my $sql = "\n\t$j ". $self->SUPER::toSQL('l');
1189 #$sql .= $_->toSQL for (@{ $self->{_right_rel}->{_join} });
1191 return $self->{_sql} = $sql;
1194 #-------------------------------------------------------------------------------------------------
1195 package OpenILS::Reporter::SQLBuilder::Join::right;
1196 use base qw/OpenILS::Reporter::SQLBuilder::Join/;
1201 #return $self->{_sql} if ($self->{_sql});
1203 my $_nullable_rel = $dir && $dir eq 'l' ? '_right_rel' : '_left_rel';
1204 $self->{_left_rel}->{_nullable} = 'r';
1205 $self->{$_nullable_rel}->{_nullable} = $dir;
1207 my $j = $dir && $dir eq 'l' ? 'FULL OUTER' : 'RIGHT OUTER';
1209 my $sql = "\n\t$j ". $self->SUPER::toSQL('r');
1211 #$sql .= $_->toSQL for (@{ $self->{_right_rel}->{_join} });
1213 return $self->{_sql} = $sql;
1216 #-------------------------------------------------------------------------------------------------
1217 package OpenILS::Reporter::SQLBuilder::Join::inner;
1218 use base qw/OpenILS::Reporter::SQLBuilder::Join/;
1223 #return $self->{_sql} if ($self->{_sql});
1225 my $_nullable_rel = $dir && $dir eq 'l' ? '_right_rel' : '_left_rel';
1226 $self->{$_nullable_rel}->{_nullable} = $dir;
1230 my $sql = "\n\t$j ". $self->SUPER::toSQL;
1232 #$sql .= $_->toSQL for (@{ $self->{_right_rel}->{_join} });
1234 return $self->{_sql} = $sql;
1237 #-------------------------------------------------------------------------------------------------
1238 package OpenILS::Reporter::SQLBuilder::Join::cross;
1239 use base qw/OpenILS::Reporter::SQLBuilder::Join/;
1243 #return $self->{_sql} if ($self->{_sql});
1245 $self->{_right_rel}->{_nullable} = 'f';
1246 $self->{_left_rel}->{_nullable} = 'f';
1248 my $sql = "\n\tFULL OUTER ". $self->SUPER::toSQL('f');
1250 #$sql .= $_->toSQL for (@{ $self->{_right_rel}->{_join} });
1252 return $self->{_sql} = $sql;