File Coverage

blib/lib/DBIx/Browse.pm
Criterion Covered Total %
statement 15 321 4.6
branch 0 102 0.0
condition 0 31 0.0
subroutine 5 26 19.2
pod 9 20 45.0
total 29 500 5.8


line stmt bran cond sub pod time code
1             #
2             # $Id: Browse.pm,v 2.9 2002/12/10 09:17:20 evilio Exp $
3             #
4             package DBIx::Browse;
5              
6 2     2   201775 use strict;
  2         4  
  2         89  
7 2     2   2738 use diagnostics;
  2         489044  
  2         199  
8 2     2   2380 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
  2         11  
  2         235  
9              
10 2     2   14 use Carp;
  2         5  
  2         163  
11 2     2   13 use DBI;
  2         6  
  2         3661  
12              
13             require Exporter;
14              
15             @ISA = qw(Exporter);
16              
17             @EXPORT = qw(
18             );
19             #
20             # Keep Revision from CVS and Perl version in paralel.
21             #
22             $VERSION = do { my @r=(q$Revision: 2.9 $=~/\d+/g); sprintf "%d."."%02d"x$#r,@r };
23              
24             #
25             # new
26             #
27             sub new {
28 0     0 1   my $this = shift;
29 0   0       my $class = ref($this) || $this;
30 0           my $self = {};
31 0           bless $self, $class;
32 0           $self->init( @_ );
33 0           return $self;
34             }
35              
36             #
37             # init
38             #
39             sub init {
40 0     0 0   my $self = shift;
41 0           my $param = shift;
42 0           my ($dbh, $table, $pkey, $pfields, $lfields , $ltables, $lvalues,
43             $lrefs, $lalias, $debug);
44              
45 0           $dbh = $param->{dbh};
46 0 0         croak 'No database handler.'
47             unless ( UNIVERSAL::isa($dbh, 'DBI::db') );
48              
49 0 0         $table = $param->{table} or croak 'No table to browse.';
50 0   0       $pkey = $param->{primary_key} || 'id';
51              
52 0           $pfields = $param->{proper_fields};
53 0           $lfields = $param->{linked_fields};
54 0           $ltables = $param->{linked_tables};
55 0           $lvalues = $param->{linked_values};
56 0           $lrefs = $param->{linked_refs};
57 0           $lalias = $param->{aliases};
58 0           $debug = $param->{debug};
59              
60            
61              
62             # try to set autocommit to 0
63 0           $self->{dbh} = $dbh;
64 0           eval { $self->{dbh}->{AutoCommit} = 0; };
  0            
65 0 0         $self->die() if ($dbh->{AutoCommit});
66 0           $self->{dbh}->{PrintError} = 0;
67 0           $self->set_syntax();
68              
69 0           $self->{table} = lc("$table");
70 0           $self->{primary_key} = $pkey;
71              
72 0           $self->{debug} = $debug;
73              
74 0           $self->{single} = 0;
75              
76 0 0         if ( ! $lfields ) {
77 0           $self->{single} = 1;
78             }
79              
80 0           $self->{linked_fields} = [map {lc($_)} @$lfields];
  0            
81              
82 0           my @fields = $self->fields;
83              
84 0 0         if ( $pfields ) {
85 0           $self->{non_linked} = [map {lc($_)} @$pfields];
  0            
86             }
87             else {
88 0           $self->{non_linked} = [];
89 0           foreach my $f ( @fields ) {
90 0           my $lnk = grep (/$f/i, @{$self->{linked_fields}});
  0            
91 0 0         if ( ! $lnk ) {
92 0           push @{$self->{non_linked}}, lc($f);
  0            
93             }
94             }
95             }
96              
97             #
98             # croak if mismatched parameters present
99             #
100 0     0     my $croak_txt = sub { my ($par,$par_ref) = @_; return "Parameter $par (if present) must have the same numbers of elements than $par_ref"; };
  0            
  0            
101              
102             # linked_tables
103 0 0 0       if ( ! $ltables ) {
  0 0          
104 0           $ltables = $self->{linked_fields};
105             }
106             elsif ( $self->{single} ||
107             scalar(@$ltables) != scalar(@{$self->{linked_fields}})
108             ) {
109 0           croak $croak_txt->('linked_tables','linked_fields');
110             }
111 0           $self->{linked_tables} = [map {lc($_)} @$ltables];
  0            
112              
113             # nuber of linked tables.
114 0           my $n_ltables = 0;
115 0 0         if ( $ltables ) {
116 0           $n_ltables = scalar(@$ltables);
117             }
118              
119             # linked_values
120 0 0 0       if ( ! $lvalues && $n_ltables ) {
    0 0        
      0        
121 0           my $names = 'name,' x $n_ltables;
122 0           my @lvalues = split(/,/, $names, $n_ltables);
123 0           $lvalues[$n_ltables-1] =~ s/,$//;
124 0           $lvalues = \@lvalues;
125             }
126             elsif ( $lvalues && (
127             $self->{single} ||
128             scalar(@$lvalues) != scalar(@{$self->{linked_fields}}))
129             ) {
130 0           croak $croak_txt->('linked_values','linked_fields');
131             }
132 0           $self->{linked_values} = [map {lc($_)} @$lvalues];
  0            
133              
134             # linked_refs
135 0 0 0       if ( ! $lrefs && $n_ltables ) {
    0 0        
      0        
136 0           my $ids = 'id,' x $n_ltables;
137 0           my @lrefs = split( /,/, $ids, $n_ltables);
138 0           $lrefs[$n_ltables-1] =~ s/,$//;
139 0           $lrefs = \@lrefs;
140             }
141             elsif ( $lrefs && (
142             $self->{single} ||
143             scalar(@$lrefs) != scalar(@{$self->{linked_fields}}))
144             ) {
145 0           croak($croak_txt->('linked_refs','linked_fields'));
146             }
147 0           $self->{linked_refs} = [map {lc($_)} @$lrefs];
  0            
148              
149             # aliases (fields)
150 0 0         if ( ! $lalias) {
    0          
151 0           $lalias = [@{$self->{non_linked}}, @{$self->{linked_tables}} ]
  0            
  0            
152             }
153             elsif ( scalar(@$lalias) != $n_ltables+1) {
154 0           croak $croak_txt->('linked_refs','linked_fields plus one');
155             }
156 0           $self->{aliases} = [map {lc($_)} @$lalias];
  0            
157              
158              
159             #
160             # table aliases: we need them to assure that the same table can be included
161             # two or more times.
162             #
163 0           my $table_alias = 'AAA';
164 0           $self->{table_aliases} = [ "$table_alias" ];
165 0           foreach my $t ( @{$self->{linked_tables}} ) {
  0            
166 0           $table_alias++; # How nice is perl
167 0           push(@{$self->{table_aliases}}, $table_alias);
  0            
168             }
169             }
170              
171             #
172             # query_fields: fields to be SELECTed by the prepare
173             #
174             sub query_fields {
175 0     0 0   my $self = shift;
176              
177 0           my $query = "";
178 0           my $nproper = scalar(@{$self->{non_linked}});
  0            
179              
180 0           for (my $f = 0; $f < $nproper; $f++) {
181 0           $query .= ', '.$self->{table_aliases}->[0].".".$self->{non_linked}->[$f].
182             ' AS '.$self->{aliases}->[$f];
183             }
184 0 0         unless ( $self->{single}) {
185 0           for (my $lf = 0; $lf < scalar(@{$self->{linked_fields}}); $lf++) {
  0            
186 0           $query .= ", ".$self->{table_aliases}->[$lf+1].".".
187             $self->{linked_values}->[$lf].
188             ' AS '.$self->{aliases}->[$nproper+$lf];
189             }
190             }
191              
192             # Include pkey always
193             $query .=
194 0           ', '.$self->{table_aliases}->[0].".".$self->{primary_key}.
195             ' AS '.$self->pkey_name.' ';
196             # remove trailing ', '
197 0           $query =~ s/^, //;
198              
199 0           return $query;
200             }
201              
202             #
203             # query_tables: FROM clase in prepare
204             #
205             sub query_tables {
206 0     0 0   my $self = shift;
207              
208 0           my $query = '';
209              
210             # tables list
211 0           $query .= "\n FROM ".$self->{table}." ".$self->{table_aliases}->[0]." ";
212 0 0         unless ( $self->{single} ) {
213 0           my $i = 1;
214 0           foreach my $lt ( @{$self->{linked_tables}} ) {
  0            
215 0           $query .= ", ".$lt." ".$self->{table_aliases}->[$i];
216 0           $i++;
217             }
218              
219             # join condition
220 0           $query .= "\n WHERE ";
221 0           for(my $lf = 0; $lf < scalar(@{$self->{linked_fields}}); $lf++) {
  0            
222 0 0         unless ($self->{linked_fields}->[$lf] =~ m/.+\..+/ ) {
223 0           $query .=
224             $self->{table_aliases}->[0].".";
225             }
226             $query .=
227 0           $self->{linked_fields}->[$lf] . " = ".
228             $self->{table_aliases}->[$lf+1].".".
229             $self->{linked_refs}->[$lf]. " AND ";
230             }
231              
232             # Erase trailing AND
233 0           $query =~ s/AND $//;
234             }
235              
236 0           return $query;
237             }
238              
239             #
240             # query: minimal query for prepare
241             #
242             sub query {
243 0     0 0   my $self = shift;
244              
245 0           my $query = "SELECT ";
246              
247 0           $query .= $self->query_fields;
248              
249 0           $query .= $self->query_tables;
250              
251 0           return $query;
252             }
253              
254              
255             #
256             # count: count fields (with prepare)
257             #
258             sub count {
259 0     0 1   my $self = shift;
260 0           my $params = shift;
261              
262 0           $params->{fields} = ' count(*) ';
263              
264 0           my $counth = $self->prepare($params);
265              
266 0           $counth->execute;
267              
268 0           my ($count) = $counth->fetchrow_array;
269              
270 0           return $count;
271             }
272              
273             #
274             # prepare: prepare a query with the fields, FROM clause and WHERE prebuilt
275             #
276             sub prepare {
277 0     0 1   my $self = shift;
278 0           my $param = shift;
279 0           my %syntax = %{$self->{syntax}};
  0            
280 0           my %order = %{$self->{syntax_order}};
  0            
281              
282 0 0         if ( $self->{single} ) {
283 0           $syntax{where} = ' WHERE ';
284             }
285              
286             # always use offset
287 0 0 0       unless ($param->{offset} or not $param->{limit} ) {
288 0           $param->{offset} = '0 ';
289             }
290            
291 0           my $query = '';
292              
293 0           foreach my $num ( sort keys %order ) {
294 0           my $p = $order{$num};
295 0 0         if ( $param->{$p} ) {
296 0           $param->{$p} = $self->unalias_fields($param->{$p});
297 0           $query .= "\n".$syntax{$p}.$param->{$p};
298 0 0         if ( ref($param->{$p}) eq 'HASH') {
299 0           print
300             "Par: ",$p,
301 0           ", Keys: ",join(':', keys %{ $param->{$p} } ),
302 0           ", Vals: ",join(':', values %{ $param->{$p} } );
303             }
304             }
305             }
306              
307 0 0         if ( $param->{fields} ) {
308 0           $query = 'SELECT '.$param->{fields}.' '.$self->query_tables();
309             }
310             else {
311 0           $query = $self->query().' '.$query;
312             }
313              
314 0           $self->debug("Prepare: ".$query."\n");
315              
316 0 0         return $self->{dbh}->prepare($query)
317             or $self->die();
318             }
319              
320             #
321             # unalias_fields: used to specify a field as table_alias.fieldname instead of
322             # field alias, i.e.: AAB.name instead of 'department' or 'department.name'.
323             #
324             sub unalias_fields {
325 0     0 0   my $self = shift;
326 0           my $phrase = shift;
327 0           my $type = shift;
328              
329 0           my $nprop = scalar (@{$self->{non_linked}});
  0            
330              
331 0           $self->debug("Alias:\t$phrase");
332 0           my ( $field, $table, $column);
333              
334 0           for (my $f = 0; $f < scalar(@{$self->{aliases}}); $f++) {
  0            
335 0           $field = $self->{aliases}->[$f];
336              
337 0 0         $table = ($f >= $nprop) ?
338             $self->{table_aliases}->[$f-$nprop+1] :
339             $self->{table_aliases}->[0];
340            
341 0 0         $column = ($f >= $nprop) ?
342             $self->{linked_values}->[$f-$nprop] :
343             $self->{non_linked}->[$f];
344             # change all occurrences of SQL
345             # word "$field" by
346             # the correct $table.$column
347 0           $phrase =~
348 0           s/(\A|[^0-9a-z_.]+)($field)([^0-9a-z_.]+)/$1.$table.".".$column.$3/egi;
349            
350             };
351             # unalias primary key
352 0           $field = $self->pkey_name;
353 0           $table = $self->{table_aliases}->[0];
354 0           $column = $self->{primary_key};
355 0           $phrase =~
356 0           s/(\A|[^0-9a-z_.]+)($field)([^0-9a-z_.]+)/$1.$table.".".$column.$3/egi;
357              
358 0           $self->debug("Unalias:\t$phrase");
359 0           return $phrase;
360             }
361              
362             #
363             # demap: translate linked_tables.linked_values to
364             # table.linked_fields (linked_refs).
365             # used by add() and update().
366             #
367             sub demap {
368 0     0 0   my $self = shift;
369 0           my $rec = shift;
370              
371 0           my $nprop = scalar(@{$self->{non_linked}});
  0            
372              
373 0 0         unless ( $self->{single} ) {
374             #for my $test ( keys %{$rec} ) {
375             # $self->debug("\t".$test.' => '.$rec->{$test})
376             #}
377 0           for(my $f = 0; $f < scalar( @{$self->{linked_fields}} ); $f++) {
  0            
378 0           my $fname = $self->{aliases}->[$f+$nprop];
379 0           my $lnk = grep( /$fname/i, keys %$rec );
380 0 0         next unless $lnk;
381 0           my $qfield =
382             "SELECT ".$self->{linked_refs}->[$f].
383             " FROM ".$self->{linked_tables}->[$f].
384             " WHERE ".$self->{linked_values}->[$f].
385             " = ?";
386 0           $self->debug('Demap: '.$qfield);
387 0 0         my $stf = $self->{dbh}->prepare($qfield)
388             or $self->die();
389 0 0         $stf->execute($rec->{$fname})
390             or $self->die();
391 0           my ($ref_value) = $stf->fetchrow_array;
392 0           delete $rec->{$fname};
393 0           $rec->{$self->{linked_fields}->[$f]} = $ref_value;
394             }
395             }
396             }
397              
398             #
399             # insert: insert a record into the main table.
400             #
401             sub insert {
402 0     0 1   my $self = shift;
403 0           my $rec = shift;
404              
405 0           my $query = 'INSERT INTO '.$self->{table}.'(';
406 0           my $qval = ' VALUES(';
407              
408 0           my @fields = $self->fields;
409              
410 0           $self->demap($rec);
411              
412 0           foreach my $f ( keys %$rec ) {
413 0           my $fok = grep (/$f/i, @fields );
414 0           $query .= $f.',';
415 0           $qval .= $self->{dbh}->quote($rec->{$f}).",";
416 0 0         next if $fok;
417 0           $self->debug("Field not found: $f => ".$rec->{$f});
418             }
419 0           chop($query);
420 0           chop($qval);
421 0           $query .= ') '.$qval.')';
422 0           $self->debug("Insert: ".$query);
423 0 0         my $ok = $self->{dbh}->do($query) or $self->die();
424 0 0         $self->{dbh}->commit() or $self->die();
425 0           return $ok;
426             }
427              
428             #
429             # update: update a row in the main table.
430             #
431             sub update {
432 0     0 1   my $self = shift;
433 0           my $rec = shift;
434 0           my $where = shift;
435 0           my @fields = $self->fields;
436              
437 0           my $query = 'UPDATE '.$self->{table}.' SET ';
438              
439 0           $self->demap($rec);
440              
441 0           foreach my $f ( keys %$rec ) {
442 0           my $fok = grep (/$f/i, @fields );
443 0           $query .= $f.' = ';
444 0           $query .= $self->{dbh}->quote($rec->{$f}).",";
445 0 0         next if $fok;
446 0           croak "Field not found: $f => ".$rec->{$f};
447             }
448 0           chop($query);
449              
450            
451 0 0         $query .= ' WHERE '.$where if ($where);
452              
453 0           $self->debug("Update: ".$query);
454 0 0         my $ok = $self->{dbh}->do($query) or $self->die();
455 0 0         $self->{dbh}->commit() or $self->die();
456 0           return $ok;
457             }
458              
459             #
460             # delete: delete a record in the main table).
461             #
462             sub delete {
463 0     0 1   my $self = shift;
464 0           my $pkey = shift;
465 0           my $qdel =
466             "DELETE FROM ".$self->{table}.
467             " WHERE ".$self->{primary_key}." = ?";
468              
469 0           $self->debug("Delete: ".$qdel. ' [ ? = '.$pkey.']');
470 0 0         my $sdel = $self->{dbh}->prepare($qdel) or $self->die();
471 0 0         my $rdel = $sdel->execute($pkey) or $self->die();
472 0 0         $self->{dbh}->commit() or $self->die();
473 0           return $rdel;
474             }
475              
476             #
477             # fields: list of fields (aliases)
478             #
479             sub fields {
480 0     0 0   my $self = shift;
481 0           my $single = "SELECT * FROM ".$self->{table}." LIMIT 1";
482 0 0         my $sth = $self->{dbh}->prepare($single) or $self->die();
483 0 0         my $rh = $sth->execute or $self->die();
484             # my $hrow = $sth->fetchrow_hashref;
485 0           my @fields = @{ $sth->{NAME_lc} };
  0            
486             # sort keys %$hrow;
487 0           return @fields;
488             }
489              
490             #
491             # field_values: obtain a list of possible field values for a linked field.
492             #
493             sub field_values {
494 0     0 1   my $self = shift;
495 0           my $field = shift;
496 0           my ($fname, $table, $id);
497 0 0         if ( $field < scalar @{$self->{non_linked}} ) {
  0            
498 0           $fname = $self->{non_linked}->[$field];
499 0           $id = $self->{primary_key};
500 0           $table = $self->{table};
501             }
502             else {
503 0           $field -= scalar @{$self->{non_linked}};
  0            
504 0           $fname = $self->{linked_values}->[$field];
505 0           $id = $self->{linked_refs}->[$field];
506 0           $table = $self->{linked_tables}->[$field];
507             }
508              
509 0           my $q = 'SELECT DISTINCT '.$fname.' FROM '.$table.' ORDER BY '.$fname.';';
510              
511             #$self->debug('Field Values:'.$q);
512              
513 0 0         my $sth = $self->{dbh}->prepare($q) or $self->die();
514 0 0         $sth->execute() or $self->die();
515              
516 0           my $rv = [];
517 0           while (my @line = $sth->fetchrow_array()){
518 0           push @{ $rv }, $line[0];
  0            
519             }
520 0           return $rv;
521             }
522              
523             #
524             # pkey_name: primary key field name
525             #
526             sub pkey_name {
527 0     0 1   my $self = shift;
528 0           return $self->{table}.'_primary_key';
529             }
530              
531             #
532             # set_syntax: set some syntax specific to some drivers. This include,
533             # clause order, clause construction (LIMIT and OFFSET), ILIKE
534             # construction and globbing character.
535             #
536             sub set_syntax {
537              
538 0     0 0   my $self = shift;
539              
540             # generic clause order
541 0           $self->{syntax_order} = {
542             1 => 'where',
543             2 => 'group',
544             3 => 'having',
545             4 => 'order',
546             5 => 'limit',
547             6 => 'offset'
548             };
549              
550             # generic syntax
551 0           $self->{syntax} = {
552             'where' => ' AND ',
553             'group' => ' GROUP BY ',
554             'having' => ' HAVING ',
555             'order' => ' ORDER BY ',
556             'limit' => ' LIMIT ',
557             'offset' => ' OFFSET ',
558             'ilike' => ' ~* ',
559             'glob' => '%'
560             };
561              
562             #
563             # Standards? Ha!
564             #
565             # mysql: the LIMIT OFFSET clauses are LIMIT [offset],limit; the LIKE
566             # is an ILIKE.
567             #
568 0 0         if ( $self->{dbh}->{Driver}->{Name} =~ m/mysql/i ) {
    0          
569 0           $self->{syntax}->{limit} = ',';
570 0           $self->{syntax}->{offset} = ' LIMIT ';
571 0           $self->{syntax}->{ilike} = ' LIKE ';
572 0           $self->{syntax_order}->{5} = 'offset';
573 0           $self->{syntax_order}->{6} = 'limit';
574             } # postgres: (I)LIKE uses regular expression operator
575             elsif ( $self->{dbh}->{Driver}->{Name} =~ m/pg/i ) {
576 0           $self->{syntax}->{ilike} = ' ~* ';
577 0           $self->{syntax}->{glob} = '';
578             }
579             # put yours here...
580             }
581              
582             #
583             # debug: print debug.
584             #
585             sub debug {
586 0     0 1   my $self = shift;
587 0 0         return (0) unless $self->{debug};
588 0           my $txt = shift;
589 0 0         print ref($self),' : ', $txt,"\n"
590             if ($txt);
591 0           return 1;
592             }
593              
594             #
595             # sprint: dump the internal structure.
596             #
597             sub sprint {
598 0     0 0   my $self = shift;
599 0           my $s = ref($self)."\n";
600 0           foreach my $tag ( sort keys %$self ) {
601 0           $s .= "\t".$tag."\n";
602 0           my $type = ref($self->{$tag});
603 0 0         if ( $type eq 'HASH') {
    0          
604 0           foreach my $k ( sort keys %{$self->{$tag}} ) {
  0            
605 0           $s .= "\t\t".$k."\t=> ".$self->{$tag}->{$k}."\n";
606             }
607             }
608             elsif ($type eq 'ARRAY') {
609 0           $s .= "\t\t(".join(",", @{$self->{$tag}}).")\n";
  0            
610             }
611             else {
612 0           $s .= "\t\t".$self->{$tag}."\n";
613             }
614             }
615 0           return("$s");
616             }
617              
618             #
619             # die: die from database errors printing the error.
620             #
621             sub die {
622 0     0 0   my $self = shift;
623 0           my $dbh = $self->{dbh};
624 0   0       my $err = $dbh->errstr || 'Unknown DBI error';
625 0           my @caller = caller;
626              
627 0           $self->print_error(
628             'Error from database: '.$err."\n".
629             'At '.$caller[0].', '.$caller[1].' line '.$caller[2].'.'
630             );
631              
632 0           $dbh->rollback();
633 0           exit();
634             }
635              
636             #
637             # print_error: print an error.
638             #
639             sub print_error {
640 0     0 0   my $self = shift;
641 0           my $error = shift;
642              
643 0           warn($error);
644             }
645              
646             #########################################################################
647             1;
648             #
649             #
650             #
651             __END__