File Coverage

blib/lib/DBIx/SQLCrosstab.pm
Criterion Covered Total %
statement 126 586 21.5
branch 46 272 16.9
condition 14 62 22.5
subroutine 13 31 41.9
pod 8 14 57.1
total 207 965 21.4


line stmt bran cond sub pod time code
1             package DBIx::SQLCrosstab;
2 1     1   31101 use strict;
  1         3  
  1         37  
3 1     1   6 use warnings;
  1         3  
  1         35  
4 1     1   5 use DBI;
  1         6  
  1         31  
5 1     1   1840 use Data::Dumper;
  1         21210  
  1         83  
6 1     1   1711 use Tree::DAG_Node;
  1         61686  
  1         18038  
7              
8             our $VERSION = '1.17';
9             # 07-Jan-2004
10              
11             require 5.006;
12              
13             require Exporter;
14             our @ISA= qw(Exporter);
15             our @EXPORT=qw();
16             our @EXPORT_OK=qw();
17              
18             our $errstr = "";
19             my $_RaiseError = 0;
20             my $_PrintError = 0;
21              
22             my %_xkeywords = (
23             dbh => 1,
24             rows => 1,
25             cols => 1,
26             op => 1,
27             op_col => 0, # DEPRECATED - KEPT for backward compatibility
28             from => 1,
29              
30             add_op => 0, # DEPRECATED - KEPT for backward compatibility
31             records => 0,
32             col_names => 0,
33              
34             where => 0,
35             having => 0,
36             title => 0,
37             remove_if_null => 0,
38             remove_if_zero => 0,
39             row_total => 0,
40             row_sub_total => 0,
41             col_total => 0,
42             col_sub_total => 0,
43             col_exclude => 0,
44              
45             complete_html_page => 0,
46             only_html_header => 0,
47              
48             add_colors => 0,
49             text_color => 0,
50             number_color => 0,
51             header_color => 0,
52             footer_color => 0,
53             table_border => 0,
54             table_cellspacing => 0,
55             table_cellpadding => 0,
56             commify => 0,
57             title_in_header => 0,
58              
59             add_real_names => 0,
60             use_real_names => 0,
61             RaiseError => 0,
62             PrintError => 0,
63             );
64              
65             my %_rowkeywords = (
66             col => 1,
67             alias => 0
68             );
69              
70             my %_colkeywords = (
71             id => 1,
72             from => 1,
73             value => 0,
74             group => 0,
75             exclude_value => 0,
76             where => 0,
77             orderby => 0,
78             col_list => 0,
79             );
80              
81             my $_stub = {
82             dbh => {dsn=>"dbi:ExampleP:test"},
83             op => [ [ 'COUNT' => 'dummy' ] ],
84             from => 'dummy',
85             cols => [ {id => 'dummy', from => 'dummy'}],
86             rows => [ {col => 'dummy'}],
87             };
88              
89             my @_operations = map {qr/^\s*$_\s*$/i}
90             (qw(count sum avg std var max min));
91              
92             sub new {
93 2     2 1 124 my $class = shift;
94 2         5 my $opt = shift;
95 2         7 my $self = bless {
96             }, $class;
97 2 50       8 return seterr("Parameters required in $class constructor")
98             unless $opt;
99 2 100       15 if (ref $opt eq 'HASH') {
    50          
100 1         7 for (keys %$opt) {
101 13         33 $self->{ $_} = $opt->{$_};
102             }
103             }
104             elsif ($opt =~/^stub$/i) {
105 1         6 for (keys %$_stub) {
106 5         13 $self->{$_} = $_stub->{$_}
107             }
108             }
109 2 50       18 if ($self->_check_allowed) {
110 2 50       9 if ($self->{RaiseError}) {
111 0         0 $_RaiseError = 1;
112             }
113 2         14 return $self->_check_required
114             }
115 0         0 return undef;
116             }
117              
118             #
119             # set_param( cols => [{id => 'mycol', from => 'mytable'}] )
120             #
121              
122             sub set_param {
123 1     1 1 613 my $self = shift;
124 1         5 while (@_) {
125 1 50       5 return seterr("odd number of parameters in set_param")
126             unless 2 <= scalar(@_) ;
127 1         2 my $param = shift;
128 1         2 my $value = shift;
129 1 50       5 if (exists $_xkeywords{$param}) {
130 1         17 $self->{$param} = $value;
131             }
132             else {
133 0         0 return seterr("unrecognized parameter $param ");
134             }
135             }
136 1         86 return $self->_check_required;
137             }
138              
139             sub op_list{
140 0     0 0 0 my $self = shift;
141 0         0 return join ",", map { uc( $_->[0]) ."($_->[1])" } @{$self->{op}};
  0         0  
  0         0  
142             }
143              
144             sub op {
145 0     0 0 0 my $self = shift;
146 0         0 return join ",", map { uc $_->[0] } @{$self->{op}};
  0         0  
  0         0  
147             }
148              
149             sub op_col {
150 0     0 0 0 my $self = shift;
151 0         0 return join ",", map { $_->[1] } @{$self->{op}};
  0         0  
  0         0  
152             }
153              
154             sub get_params {
155 1     1 1 2 my $self = shift;
156 1         2 my $params_name = "params";
157 1         3 my %params =();
158 1         9 for (keys %_xkeywords) {
159 35 100       67 next if /^dbh$/;
160 34 100 66     115 if (exists $self->{$_} and defined($self->{$_})) {
161 12         30 $params{$_} = $self->{$_};
162             }
163             }
164 1         5 local $Data::Dumper::Indent = 1;
165 1         14 return Data::Dumper->Dump([\%params],[$params_name]);
166             }
167              
168             sub save_params {
169 1     1 1 23 my $self = shift;
170 1   50     5 my $param_file_name = shift || "xtab_params.pl";
171 1 50       173 open PARAMS, "> $param_file_name"
172             or return seterr("can't open $param_file_name");
173 1         13 print PARAMS $self->get_params();
174 1 50       1268 close(PARAMS) or return seterr("can't close $param_file_name");
175 1         6 return 1;
176             }
177              
178             sub load_params {
179 1     1 1 26 my $self = shift;
180 1         2 my $params_file_name = shift;
181 1         2 my $params = undef;
182 1 50       4 return seterr("filename required to load_params()")
183             unless $params_file_name;
184 1 50       45 open PARAMS, "< $params_file_name"
185             or return seterr("can't open $params_file_name");
186 1         4 $errstr = undef;
187             {
188 1         3 local $/;
  1         7  
189 1         25 my $value = ;
190 1 50       6 if ($value) {
191 1         366 eval $value;
192 1 50       8 if ($@) {
193 0         0 seterr("error retrieving parameters from $params_file_name");
194             }
195 1 50       6 seterr("no params found in $params_file_name")
196             unless $params;
197             }
198             else {
199 0         0 seterr("no params found in $params_file_name")
200             }
201             }
202 1 50       18 close(PARAMS) or return seterr("can't close $params_file_name");
203 1 50       4 return undef if $errstr;
204 1 50       7 return seterr("invalid parameters in $params_file_name")
205             unless ref($params) eq 'HASH';
206 1         7 for (keys %$params) {
207 12 50       25 return seterr("unrecognized option ($_) in file $params_file_name")
208             unless exists $_xkeywords{$_};
209 12         27 $self->{$_} = $params->{$_};
210             }
211 1         6 return $self->_check_required;
212             }
213              
214             sub seterr {
215 0   0 0 0 0 my $msg = shift || "-- no msg --";
216 0         0 $errstr = $msg ;
217 0 0       0 if ($_RaiseError) {
    0          
218 0         0 die "$msg\n ";
219             }
220             elsif ($_PrintError) {
221 0         0 warn "$msg\n";
222             }
223 0         0 return undef;
224             }
225              
226             sub _check_allowed{
227 2     2   5 my $self = shift;
228 2   33     10 $_RaiseError = (exists($self->{RaiseError}) && $self->{RaiseError} ) ;
229 2   33     9 $_PrintError = (exists($self->{PrintError}) && $self->{PrintError});
230 2         10 for (keys %$self) {
231 18 50       48 return seterr("unrecognized option '$_'")
232             unless defined $_xkeywords{$_} ;
233             }
234 2 50       8 if ($self->{col_exclude}) {
235 0 0       0 return seterr ("list required with parameter 'col_exclude'")
236             unless ref $self->{col_exclude} eq 'ARRAY';
237             }
238 2         186 return $self;
239             }
240              
241             sub _check_required_kw {
242 0     0   0 my ($set, $kw, $opt) = @_;
243 0         0 for (grep {$kw->{$_}} keys %$kw)
  0         0  
244             {
245 0 0       0 return seterr("required $opt '$_' not defined")
246             unless defined $set->{$_}
247             }
248 0         0 return $set;
249             }
250              
251             sub _check_required {
252 4     4   10 my $self = shift;
253 4         82 for (grep {$_xkeywords{$_}} keys %_xkeywords) {
  140         179  
254 20 50       48 return seterr("required option '$_' not defined")
255             unless defined $self->{$_};
256             }
257 4 50       21 if (defined $self->{dbh}) {
258 4 100 66     48 if ( ref($self->{dbh})
    50 33        
259             && ( (ref $self->{dbh}) =~ 'DBI::db' ))
260             {
261             # OK
262             }
263             elsif (ref($self->{dbh})
264             && ( ref( $self->{dbh}) eq "HASH"))
265             {
266 3         6 my $par = $self->{dbh};
267 3         4 my $dbh;
268 3 50       5 eval {$dbh = DBI->connect($par->{dsn},
  3         34  
269             $par->{user}, $par->{password},
270             $par->{params}) or die "$DBI::errstr"
271             };
272 3 50       6580 if ($@) {
273 0         0 return seterr("error in connection $@")
274             }
275             else {
276 3         12 $self->{dbh} = $dbh;
277             }
278             }
279             else
280             {
281 0         0 return seterr("invalid \$dbh parameter")
282             }
283             }
284             else {
285 0         0 return seterr("\$dbh parameter required")
286             }
287 4         8 for my $row (@{$self->{rows}}) {
  4         92  
288 6         18 for (grep {$_rowkeywords{$_}} keys %_rowkeywords) {
  12         28  
289 6 50 33     39 return seterr(
290             "missing required parameter ($_) in row definition")
291             unless exists $row->{$_}
292             && defined $row->{$_}
293             }
294 6         19 for (keys %$row) {
295 8 50       37 return seterr("unrecognized row parameter ($_)")
296             unless exists $_rowkeywords{$_};
297             }
298             }
299 4         8 for my $col (@{$self->{cols}}) {
  4         12  
300 8         27 for (grep {$_colkeywords{$_}} keys %_colkeywords) {
  64         89  
301 16 50 33     84 return seterr(
302             "missing required parameter ($_) in column definition")
303             unless exists $col->{$_}
304             && defined $col->{$_}
305             }
306 8         31 for (keys %$col) {
307 22 50       56 return seterr("unrecognized row parameter ($_)")
308             unless exists $_colkeywords{$_};
309             }
310             }
311 4         9 my $op_allowed = 0;
312            
313 4 50       26 unless ( ref($self->{op}) )
314             # compatibility code for {op}
315             {
316 0         0 my $tmpop;
317 0 0       0 return seterr("Parameter 'op_col' undefined")
318             unless defined $self->{op_col};
319 0         0 push @$tmpop, [ $self->{op}, $self->{op_col}];
320 0 0       0 if ($self->{add_op}) {
321 0 0 0     0 return seterr("Parameter 'add_op' must be an array reference")
322             unless (ref($self->{add_op})
323             && (ref($self->{add_op}) eq 'ARRAY'));
324 0         0 for my $aop(@{$self->{add_op}}) {
  0         0  
325 0         0 push @$tmpop, [$aop, $self->{op_col}];
326             }
327 0         0 delete $self->{add_op};
328             }
329 0         0 delete $self->{op_col};
330 0         0 $self->{op} = $tmpop;
331             }
332              
333 4 50       12 return seterr("Parameter 'op' must be an array reference")
334             unless ref($self->{op}) eq 'ARRAY';
335 4         6 for my $op (@{$self->{op}}) {
  4         10  
336             return
337 4 50 33     25 seterr("All items in parameter {op} must be array references")
338             unless (ref($op) && (ref($op) eq 'ARRAY'));
339 4         8 for my $item (@_operations) {
340 196         717 return seterr("unrecognized operator $op->[0]")
341 28 50       36 unless grep { $op->[0] =~ $_ } @_operations;
342 28 50       69 return seterr("Invalid opertator definition (@{$op})")
  0         0  
343             unless @$op eq 2;
344             }
345 4 50       6 if (scalar @{$self->{op}} > 1) {
  4         24  
346 0         0 $self->{col_total} = 0;
347             }
348             }
349            
350             #for my $op (@_operations) {
351             # if ($self->{op} =~ $op ) {
352             # $op_allowed =1;
353             # last;
354             # }
355             #}
356             #return seterr("operation not allowed (" . $self->{op} . ")")
357             # unless $op_allowed;
358             #if ($self->{add_op}) {
359             # if (ref $self->{add_op} eq 'ARRAY') {
360             # my %seen =();
361             # my @ops = grep {
362             # ($_ ne $self->{op})
363             # and (not $seen{$_}++) } @{$self->{add_op}};
364             # if (@ops) {
365             # $self->{col_total} = 0;
366             # $self->{add_op} = \@ops;
367             # }
368             # else {
369             # $self->{add_op} = undef;
370             # }
371             # }
372             # elsif (lc($self->{add_op}) eq lc($self->{op}))
373             # {
374             # $self->{add_op} = undef;
375             # }
376             # else {
377             # $self->{col_total} = 0;
378             # }
379             #}
380 4 50 33     16 if ($self->{add_real_names} and $self->{use_real_names}) {
381 0         0 $self->{add_real_names} = 0;
382             }
383 4         24 return $self;
384             }
385              
386             # _permute function written by Randal L. Schwartz,
387             # aka merlyn
388             # http://www.perlmonks.org/index.pl?node_id=24270
389             #
390             sub _permute {
391 0     0   0 my $last = pop @_;
392 0 0       0 unless (@_) {
393 0         0 return map [$_], @$last;
394             }
395 0         0 return map { my $left = $_; map [@$left, $_], @$last } _permute(@_);
  0         0  
  0         0  
396             }
397              
398             # _permute_group is not permuting anything, actually,
399             # since the data coming from the distinct query
400             # is already a permutation. The only task performed here
401             # is returning the appropriate structure.
402             sub _permute_group {
403 0     0   0 my $array = shift;
404 0         0 my @permutations;
405 0         0 for my $row (@$array) {
406 0         0 push @permutations, [map { {xcol_id => $_, xcol_alias=> $_} } @$row ];
  0         0  
407             }
408 0         0 return \@permutations;
409             }
410              
411             # _add_values fills the tree to create
412             # the appropriate permutations
413             #
414             sub _add_values {
415 0     0   0 my ($top, $array, $level) = @_;
416 0 0       0 return if $level > $#$array;
417 0         0 my $values = $array->[$level];
418             $top->new_daughter
419 0         0 ->attributes( {contents => $_} ) for @$values;
420 0         0 _add_values($_, $array, $level+1) for $top->daughters;
421             }
422              
423             # _add_group_values fills the tree without
424             # permutations, which were already found
425             # in the DISTINCT query
426             #
427             sub _add_group_values {
428 0     0   0 my ($tr, $array) = @_;
429 0         0 for my $row (@$array) {
430 0         0 my $top = $tr;
431 0         0 for my $col(@$row) {
432 0         0 my $node = undef;
433 0 0       0 if ($top->daughters) {
434 0         0 ($node) = grep {$_->name eq $col} $top->daughters;
  0         0  
435             }
436 0 0       0 unless ($node) {
437 0         0 $node = Tree::DAG_Node->new;
438 0         0 $node->name($col);
439 0         0 $node->attributes( {contents => {
440             xcol_id => $col, xcol_alias=>$col
441             }} );
442 0         0 $top->add_daughter($node)
443             }
444 0         0 $top = $node;
445             }
446             }
447             }
448              
449             #
450             # _xpermute creates a permutation tree
451             #
452             sub _xpermute {
453 0     0   0 my $array = shift;
454 0   0     0 my $mode = shift || "normal";
455 0         0 my $tree = Tree::DAG_Node->new;
456 0         0 $tree->name('xtab');
457 0 0       0 if ($mode eq "normal") {
    0          
458 0         0 _add_values($tree, $array, 0);
459             }
460             elsif ($mode eq "group") {
461 0         0 _add_group_values ($tree,$array)
462             }
463             else {
464 0         0 return seterr("unrecognized tree-filling mode ($mode)");
465             }
466             #print map {"$_\n"} @{$tree->draw_ascii_tree}; exit;
467 0         0 my @permuted;
468             $tree->walk_down (
469             {
470             callbackback => sub {
471 0     0   0 my $node = shift;
472 0 0       0 return 1 unless $node->ancestors;
473 0         0 push @permuted,
474             [
475             reverse
476 0         0 map {$_->attributes->{contents}}
477 0         0 grep {$_->address ne '0'}
478             $node, $node->ancestors
479             ];
480 0         0 1;
481             }
482             }
483 0         0 );
484 0         0 $tree->delete_tree;
485 0         0 return \@permuted;
486             }
487              
488             sub from {
489 0     0 1 0 my $self = shift;
490 0         0 my $val = shift;
491 0 0       0 if ($val) {
492 0         0 $self->{from} = $val;
493 0         0 $self->rows($self->{rows});
494             }
495 0         0 return $self->{from};
496             }
497              
498             sub rows {
499 0     0 0 0 my $self = shift;
500 0         0 my $val = shift;
501 0 0       0 if ($val) {
502 0         0 $self->{rows} = $val;
503 0         0 $self->{query} = "";
504 0         0 $self->{recs} = undef;
505 0         0 $self->columns($self->{cols});
506             }
507 0         0 return $self->{rows};
508             }
509              
510             sub columns {
511 0     0 0 0 my $self = shift;
512 0         0 my $val = shift;
513 0 0       0 if ($val) {
514 0         0 $self->{cols} = $val;
515 0         0 $self->{xvalues} = undef;
516             }
517 0         0 return $self->{cols};
518             }
519              
520             # _check_query_separator_applicability checks
521             # if the query separator is present in any of the
522             # column values, changing the separator if
523             # necessary
524             #
525             sub _check_query_separator_applicability {
526 0     0   0 my $self = shift;
527 0         0 my $permutations =shift;
528 0         0 my $separator = shift;
529 0         0 my %words =();
530 0         0 for my $row (@{$self->{rows}}) {
  0         0  
531 0         0 for (qw(id alias value)) {
532 0 0       0 if (exists $row->{$_}) {
533 0         0 $words{$row->{$_}}++;
534             }
535             }
536             }
537 0         0 for my $p (@$permutations) {
538 0         0 $words{$_}++ for @$p;
539             }
540 0 0       0 if ($separator =~ /[a-z?*+]/i) {
541 0         0 $separator ='#';
542             }
543 0         0 my @separators = ( '#', '/', '-', '=', ',');
544 0 0       0 unless (grep {$_ eq $separator} @separators) {
  0         0  
545 0         0 unshift @separators, $separator;
546             }
547 0         0 my $ok = 0;
548 0         0 my $count =0;
549 0         0 while (! $ok) {
550             SEPARATOR:
551 0         0 for my $sep (@separators) {
552 0         0 $ok =1;
553 0         0 for my $k (keys %words) {
554 0 0       0 if ($k =~ /\Q$sep\E/) {
555 0         0 $ok =0;
556 0         0 next SEPARATOR;
557             }
558             }
559 0 0       0 if ($ok) {
560 0         0 $separator = $sep;
561 0         0 last;
562             }
563             }
564 0 0       0 if (! $ok) {
565 0         0 @separators = map {$_ . substr($_,0,1) } @separators;
  0         0  
566 0 0       0 if ($count++ > 3 ) {
567 0         0 return seterr("unable to find a suitable column separator char");
568             }
569             }
570             }
571 0         0 return $separator;
572             }
573              
574             #
575             # gets the values for the column headers
576             #
577             sub _get_xvalues {
578 0     0   0 my $self = shift;
579 0         0 my $dbh = $self->{dbh};
580              
581 0         0 my @xvalues;
582 0         0 $self->{xvalues} = undef;
583             #
584             # group values required
585             # columns are evaluated in a unique query
586             # rather than separately
587             #
588 0 0       0 if (grep {exists $_->{group}} @{$self->{cols}})
  0         0  
  0         0  
589             {
590 0         0 my $colquery = qq{SELECT DISTINCT };
591 0         0 my $fieldlist = "";
592              
593 0 0       0 $fieldlist = join ", ", map {
594 0         0 $_->{id} . (exists $_->{alias}? " AS $_->{alias}" : "") }
595 0         0 @{$self->{cols}};
596 0         0 $colquery .= "$fieldlist\n";
597 0 0       0 my ($from) = map {$_->{from}}
  0         0  
598 0         0 grep {$_->{from} and ($_->{from} ne "1") }
599 0         0 @{$self->{cols}};
600 0         0 $from =~ s/^\s*from//i;
601 0         0 $colquery .= qq{ FROM $from\n};
602 0         0 my ($orderby) = map {$_->{orderby}}
  0         0  
603 0         0 grep {$_->{orderby} }
604 0         0 @{$self->{cols}};
605 0 0       0 if ($orderby) {
606 0         0 $orderby =~ s/^\s*order by//i;
607 0         0 $colquery .= qq{ ORDER BY $orderby\n}
608             }
609 0         0 my $sth;
610             my $colrecs;
611             #print $colquery,$/;
612 0         0 eval {
613 0         0 $sth = $dbh->prepare($colquery);
614 0         0 $sth->execute;
615             };
616 0 0       0 if ($@) {
617 0         0 return seterr("Error building group column query ($@)");
618             }
619 0         0 eval {
620 0         0 $colrecs = $sth->fetchall_arrayref;
621             };
622 0 0       0 if ($@) {
623 0         0 return seterr("Error retrieving group column records ($@)");
624             }
625 0         0 $self->{colrecs} = $colrecs;
626 0         0 for my $r (@$colrecs) {
627 0         0 my $count =0;
628 0         0 for my $c (@$r) {
629 0         0 push @{$xvalues[$count++]}, { xcol_id => $_ , xcol_alias => $_};
  0         0  
630             }
631             }
632 0         0 $self->{use_group} = 1;
633             }
634             else
635             # column values
636             # are retrieved separately
637             # and stored in a bi-dimensional array
638             #
639             {
640 0         0 for (@{$self->{cols}}) {
  0         0  
641 0         0 my $xvals;
642 0 0       0 my $xcol_alias = exists $_->{alias} ? $_->{alias} : "xcol_alias";
643             #
644             # if a list of values is provided
645             # then no query is issued, but the values are
646             # simply stored in the array
647             #
648 0 0       0 if ($_->{col_list}) {
649 0         0 my $list = $_->{col_list};
650 0 0       0 unless (ref $list eq 'ARRAY') {
651 0         0 return seterr("list of values expected in parameter 'col_list'");
652             }
653 0         0 for my $val (@$list) {
654 0 0       0 return seterr("elements in {col_list} must be hash references")
655             unless (ref $val eq 'HASH');
656 0 0       0 return seterr("elements in {col_list} must have an {id} key")
657             unless (defined $val->{id} );
658 0 0       0 unless (defined $val->{value}) {
659 0         0 $val->{value} = $val->{id};
660             }
661 0         0 push @$xvals, {xcol_id=> $val->{id}, $xcol_alias => $val->{value} };
662             }
663             }
664             else
665             # normal operation
666             # The values are retrieved from the database
667             # with a query
668             {
669 0         0 my $fields = qq[$_->{id} AS xcol_id];
670 0 0       0 if (exists $_->{value}) {
671 0         0 $fields .= qq[, $_->{value} AS $xcol_alias] ;
672             }
673 0         0 my $colquery = qq[SELECT DISTINCT $fields FROM $_->{from}];
674 0 0       0 if (exists $_->{where} ) {
675 0         0 $_->{where} =~ s/^\s*where\b//i;
676 0         0 $colquery .= " WHERE ". $_->{where} ." ";
677             }
678 0 0       0 if (exists $_->{orderby} ) {
679 0         0 $_->{orderby} =~ s/^\s*order\s+by\b//i;
680 0         0 $colquery .= " ORDER BY ". $_->{orderby} ." ";
681             }
682 0         0 my $sth;
683 0         0 eval {
684 0         0 $sth = $dbh->prepare($colquery);
685 0         0 $sth->execute;
686             };
687 0 0       0 if ($@) {
688 0         0 return seterr
689             "error while retrieving column values for $_->{id}\n"
690             . qq(query: "$colquery"\n)
691             . "error: $DBI::errstr\n";
692             }
693 0         0 eval { $xvals = $sth->fetchall_arrayref({}) };
  0         0  
694 0 0       0 if ($@) {
695 0         0 return seterr("Error while fetching column values "
696             ."($DBI::errstr)");
697             }
698 0 0       0 unless (exists $_->{value}) {
699 0         0 for my $row (@$xvals) {
700 0         0 $row->{$xcol_alias} = $row->{xcol_id};
701             }
702             }
703             }
704             #
705             # remove values if required
706             #
707 0 0       0 if ($_->{exclude_value}) {
708 0 0       0 unless (ref $_->{exclude_value} eq 'ARRAY') {
709 0         0 return seterr(
710             "list of value expected in parameter"
711             . " '\$cols->{exclude_value}'");
712             }
713 0         0 my @copy_xvals;
714 0         0 my %exclude_value = map {$_, 1} @{$_->{exclude_value}};
  0         0  
  0         0  
715 0   0     0 @copy_xvals = grep {
716 0         0 not (exists ($exclude_value{$_->{xcol_id}})
717             or exists ($exclude_value{ $_->{$xcol_alias} }) )
718             } @$xvals;
719 0         0 $xvals = \@copy_xvals;
720             }
721 0         0 push @xvalues, $xvals;
722 0 0       0 my $label = exists $_->{value} ? $_->{value} : $_->{id};
723 0         0 push @{$self->{xvalues}}, { label => $label, value => $xvals};
  0         0  
724             }
725             }
726 0         0 return \@xvalues;
727             }
728              
729             sub get_query {
730 0     0 1 0 my $self = shift;
731 0   0     0 my $separator = shift || '#';
732 0 0       0 return undef unless $self->_check_required;
733 0         0 my $dbh = $self->{dbh};
734              
735 0 0       0 my $xvalues = $self->_get_xvalues
736             or return undef; # seterr is already called with the real reason
737              
738 0         0 for my $row ( @{$self->{rows}} ) {
  0         0  
739 0 0       0 $row->{alias} = $row->{col} unless $row->{alias}
740             }
741 0         0 my $xrows = join ", ",
742 0         0 map { "$_->{col} AS $_->{alias}" }
743 0         0 @{$self->{rows}};
744              
745 0         0 my $query = qq{SELECT $xrows \n};
746              
747 0         0 my @xcols;
748              
749             my @permutations;
750 0 0       0 if ($self->{use_group}) {
    0          
751 0 0       0 if ( $self->{col_sub_total}) {
752 0         0 @permutations = @{_xpermute($self->{colrecs}, "group")};
  0         0  
753             }
754             else {
755 0         0 @permutations = @{_permute_group( $self->{colrecs} ) };
  0         0  
756             }
757             }
758             elsif ($self->{col_sub_total}) {
759 0         0 @permutations = @{_xpermute($xvalues)};
  0         0  
760             }
761             else {
762 0         0 @permutations = _permute(@$xvalues);
763             }
764             #for (@permutations) { print "@$_\n"; } exit;
765 0 0       0 $self->{query_separator} =
766             $self->_check_query_separator_applicability(\@permutations, $separator)
767             or return undef;
768              
769 0         0 my %realnames =();
770 0         0 my $col_count ="xfld001";
771              
772 0         0 for my $op_pair (@{$self->{op}})
  0         0  
773             {
774 0         0 my ($operator, $opcolumn) = @$op_pair;
775 0         0 for my $val (@permutations)
776             {
777 0         0 my @cn = @{$self->{cols}};
  0         0  
778 0         0 my $condition = join " AND ",
779             map {
780 0         0 "$cn[$_]->{id} = "
781             . ($dbh->quote($val->[$_]->{xcol_id}))
782             ." "
783             }
784             (0 .. $#$val);
785 0         0 my $name = join $self->{query_separator},
786 0         0 map {$val->[$_]->{xcol_alias}}
787             (0..$#$val);
788 0         0 next if ($self->{col_exclude} and
789 0 0 0     0 ( grep {$name eq $_} @{$self->{col_exclude}} ));
  0         0  
790 0 0 0     0 if ($self->{check_group} and $self->{keepcols}) {
791 0 0       0 next unless grep { $name =~ /^$_/ } @{$self->{keepcols}};
  0         0  
  0         0  
792             }
793             #
794             # name manipulation
795             #
796 0 0       0 if (@{$self->{op}} > 1 ) {
  0         0  
797 0         0 $name = "x" . lc($operator)
798             . $self->{query_separator} . $name ;
799             }
800 0 0       0 if ($self->{use_real_names}) {
801 0         0 $name = $dbh->quote($name);
802             }
803             else {
804 0         0 $realnames{$col_count} = $name;
805 0         0 $name = $col_count;
806 0         0 $col_count++;
807             }
808             #
809             #
810             #
811 0         0 my $line =
812             qq[,$operator(CASE WHEN $condition THEN ]
813             .qq[ $opcolumn ELSE NULL END) AS $name ] ;
814 0 0       0 if ($self->{add_real_names} )
815             {
816 0         0 $line .= qq[ -- ($realnames{$name}) \n];
817             }
818             else {
819 0         0 $line .= "\n";
820             }
821              
822 0         0 push @xcols, $line;
823             }
824 0 0 0     0 if ((@{$self->{op}} > 1) and $self->{col_sub_total})
  0         0  
825             {
826 0         0 my $opname = "x".lc($operator);
827 0         0 my $line = qq[,$operator($opcolumn) AS $opname\n];
828 0         0 push @xcols, $line;
829             }
830             }
831 0 0       0 if (@{$self->{op}} > 1) {
  0         0  
832 0         0 unshift @{$self->{cols}}, { id => 'op', value=>'op',
  0         0  
833 0         0 col_list => [map {"x". lc($_->[0])} @{$self->{op}} ] }
  0         0  
834             }
835 0         0 $self->{realnames} = \%realnames;
836              
837 0         0 $self->{from} =~ s/^\s*from\b//i;
838 0 0       0 if ($self->{where}) {
839 0         0 $self->{where} =~ s/^\s*where\b//i;
840             }
841 0 0       0 if ($self->{having}) {
842 0         0 $self->{having} =~ s/^\s*having\b//i;
843             }
844              
845 0         0 $query .= $_ for @xcols;
846 0         0 my $total1 = 'total';
847 0 0       0 if ($self->{col_total}) {
848 0         0 my ($operator, $opcolumn) = @{$self->{op}->[0]};
  0         0  
849 0         0 $query .= qq[,$operator($opcolumn) AS $total1\n];
850             }
851 0         0 $query .= qq[ FROM $self->{from}\n ];
852 0 0       0 if ($self->{where}) {
853 0         0 $query.= " WHERE ". $self->{where} . " \n";
854             }
855 0         0 $query .= qq[GROUP BY ]
856 0         0 . join(", ", map {$_->{alias}} @{$self->{rows}})
  0         0  
857             . "\n";
858              
859 0 0       0 if ($self->{having}) {
860 0         0 $query.= " HAVING ". $self->{having} . " \n";
861             }
862              
863 0         0 my $numrows = @{$self->{rows}} -1;
  0         0  
864 0         0 my $nr = $numrows;
865 0 0       0 if ($self->{row_sub_total}) {
866 0         0 for my $row (0..$numrows -1) {
867 0         0 $xrows = join ", ",
868             map {
869 0         0 my $val = $self->{rows}->[$_]->{col};
870 0 0       0 $val ="'zzzz'" if $_ >= $nr;
871 0         0 "$val AS $self->{rows}->[$_]->{alias}"
872             }
873             (0..$numrows);
874 0         0 $nr--;
875 0         0 $query .= qq{UNION\n SELECT $xrows \n};
876 0         0 $query .= $_ for @xcols;
877 0 0       0 if ($self->{col_total} ) {
878 0         0 my ($operator, $opcolumn) = @{$self->{op}->[0]};
  0         0  
879 0         0 $query .= qq[,$operator($opcolumn) AS $total1\n];
880             }
881 0         0 $query .= qq[ FROM $self->{from}\n ];
882 0         0 $xrows = join ", ",
883             map {
884 0         0 $self->{rows}->[$_]->{alias}
885             }
886             (0 .. $nr);
887              
888 0 0       0 if ($self->{where}) {
889 0         0 $query.= " WHERE ". $self->{where} . " \n";
890             }
891 0         0 $query .= qq{GROUP BY $xrows\n};
892 0 0       0 if ($self->{having}) {
893 0         0 $query.= " HAVING ". $self->{having} . " \n";
894             }
895             }
896             }
897              
898 0 0       0 if ($self->{row_total}) {
899 0         0 $xrows = join ", ",
900 0         0 map {"'zzzz' AS $self->{rows}->[$_]->{alias}" }
901             (0..$numrows);
902 0         0 $query .= qq{UNION\n SELECT $xrows\n};
903 0         0 $query .= $_ for @xcols;
904 0 0       0 if ($self->{col_total}) {
905 0         0 my ($operator, $opcolumn) = @{$self->{op}->[0]};
  0         0  
906 0         0 $query .= qq[,$operator($opcolumn) AS $total1\n];
907             }
908 0         0 $query .= qq[ FROM $self->{from}\n ];
909              
910 0 0       0 if ($self->{where}) {
911 0         0 $query.= " WHERE ". $self->{where} . " \n";
912             }
913             }
914              
915 0         0 $xrows = join ", ",
916 0         0 map { $self->{rows}->[$_]->{alias} } (0..$numrows);
917 0         0 $query .= qq[ORDER BY $xrows\n];
918 0         0 $query =~ s/ +/ /g;
919 0         0 $query =~ s/\n\s*\n/\n/sg;
920 0         0 $query =~ s/^ +//g;
921 0         0 $self->{query} = $query;
922 0         0 return $query;
923             }
924              
925             sub _max {
926 0     0   0 my $max = 0;
927 0         0 for (@_) {
928 0 0       0 $max = $_ if $_ > $max;
929             }
930 0         0 return $max;
931             }
932              
933             sub get_recs {
934 2     2 1 60 my $self = shift;
935 2 50 33     17 if ($self->{records} && $self->{col_names}) {
936 2 50       7 return seterr("Parameter 'col_names' must be an array reference")
937             unless (ref($self->{col_names}) eq 'ARRAY');
938 2 50 33     17 return seterr("Parameter 'records' must be an array reference")
939             unless (ref($self->{records}) eq 'ARRAY')
940             and (ref($self->{records}->[0]) eq 'ARRAY');
941 2         5 $self->{query} = qq{SELECT 'DUMMY'};
942 2         5 $self->{recs} = $self->{records};
943 2         9 $self->{NAME} = $self->{col_names};
944 2         4 $self->{query_separator} = '#';
945 2         7 return $self->{recs};
946             }
947 0 0         return seterr("call to get_recs() without get_query()")
948             unless $self->{query};
949 0           my $sth;
950 0           local $self->{dbh}->{RaiseError} = 1;
951 0           local $self->{dbh}->{PrintError} = 0;
952 0           eval {
953 0           $sth = $self->{dbh}->prepare($self->{query});
954             };
955 0 0         if ($@) {
956             return
957 0           seterr "error preparing Crosstab query ($DBI::errstr)\n";
958             }
959 0           eval {
960 0           $sth->execute;
961             };
962 0 0         if ($@) {
963             return
964 0           seterr "error executing Crosstab query ($DBI::errstr)\n";
965             }
966              
967 0 0         my @fnames = map {exists $self->{realnames}{$_} ?
  0            
968 0           $self->{realnames}{$_} : $_ } @{$sth->{NAME}};
969 0           my @lengths = map {
970 0           my @L = map {length $_ } split $self->{query_separator}, $_;
  0            
971 0           _max( @L)
972             } @fnames;
973              
974 0           my $numfields = $sth->{NUM_OF_FIELDS};
975              
976 0           my $recs;
977 0           eval {$recs = $sth->fetchall_arrayref()};
  0            
978 0 0         if ($@) {
979 0           return seterr ("error fetching records ($DBI::errstr)")
980             }
981 0 0         if($self->{remove_if_zero}) {
982 0 0         my @zeroes = map {defined $_? 0 : 1 } @{$recs->[0]} ;
  0            
  0            
983 0           for my $r (@$recs) {
984 0           my $count =0;
985 0           for my $c (@$r) {
986 0 0 0       if (defined( $c) && ($c ne "0")) {
987 0           $zeroes[$count] = 1;
988             }
989 0           $count++;
990             }
991             }
992 0           my @voids;
993             my @filled;
994 0           for (0..$#zeroes){
995 0 0         if ( $zeroes[$_] )
996             {
997 0           push @filled, $_
998             }
999             else {
1000 0           push @voids, $_
1001             }
1002             }
1003 0 0         if (@voids) {
1004 0           @fnames = @fnames[@filled];
1005 0           $numfields -= @voids;
1006 0           for my $rec (@$recs) {
1007 0           $rec = [@$rec[@filled]];
1008             }
1009 0           @lengths = @lengths[@filled];
1010             }
1011             }
1012              
1013 0 0         if($self->{remove_if_null}) {
1014 0           my @nulls = map {0} @{$recs->[0]} ;
  0            
  0            
1015 0           for my $r (@$recs) {
1016 0           my $count =0;
1017 0           for (@$r) {
1018 0 0         if (defined $_) {
1019 0           $nulls[$count] = 1;
1020             }
1021 0           $count++;
1022             }
1023             }
1024 0           my @voids;
1025             my @filled;
1026 0           for (0..$#nulls){
1027 0 0         if ( $nulls[$_] )
1028             {
1029 0           push @filled, $_
1030             }
1031             else {
1032 0           push @voids, $_
1033             }
1034             }
1035 0 0         if (@voids) {
1036 0           @fnames = @fnames[@filled];
1037 0           $numfields -= @voids;
1038 0           for my $rec (@$recs) {
1039 0           $rec = [@$rec[@filled]];
1040             }
1041 0           @lengths = @lengths[@filled];
1042             }
1043             }
1044              
1045 0           for (my $i = 0 ; $i < $numfields; $i++) {
1046 0           for (@$recs) {
1047 0 0         my $len = $_->[$i] ? (length($_->[$i])) : 0;
1048 0 0 0       if (defined($_->[$i]) && $_->[$i] =~ /^\d+\.(\d+)/)
1049             {
1050 0 0         if (length($1) > 2) {
1051 0           $len -= (length($1) - 2);
1052             }
1053             }
1054 0 0         $lengths[$i] = 0 unless defined $lengths[$i];
1055 0 0         $len = 0 unless defined $len;
1056 0 0         $lengths[$i] = $len
1057             if $lengths[$i] < $len;
1058             }
1059             }
1060 0           $self->{recs} = $recs;
1061 0           $self->{NAME} = \@fnames;
1062 0           $self->{LENGTH} = \@lengths;
1063 0           $self->{NUM_OF_FIELDS} = $numfields;
1064 0           return $recs;
1065             }
1066             1;
1067              
1068             __END__