File Coverage

blib/lib/IMDB/Local/DB/Base.pm
Criterion Covered Total %
statement 27 527 5.1
branch 0 260 0.0
condition 0 39 0.0
subroutine 9 42 21.4
pod 28 30 93.3
total 64 898 7.1


line stmt bran cond sub pod time code
1             package IMDB::Local::DB::Base;
2              
3 1     1   12 use 5.006;
  1         2  
  1         26  
4 1     1   3 use strict;
  1         1  
  1         24  
5 1     1   3 use warnings;
  1         1  
  1         19  
6 1     1   3 use Carp;
  1         1  
  1         55  
7              
8             =head1 NAME
9              
10             IMDB::Local::DB::Base - Object to manage database abstraction with handy entry points.
11              
12             =head1 VERSION
13              
14             Version 0.01
15              
16             =cut
17              
18             our $VERSION = '1.00';
19              
20              
21             =head1 SYNOPSIS
22              
23             This module presents one set of handy subroutines around a DBI connection. The interface is not well document, only as much as it is needed for the IMDB::Local::DB developer requires. It is meant to be extended.
24              
25              
26             =head1 SUBROUTINES/METHODS
27              
28             =head2 new
29              
30             =cut
31              
32 1     1   1249 use DBI;
  1         11698  
  1         43  
33 1     1   453 use Time::HiRes;
  1         982  
  1         3  
34 1     1   404 use IMDB::Local::DB::RecordIterator;
  1         1  
  1         130  
35              
36             use Class::MethodMaker
37             [
38             scalar => ['dbh', 'modCount'],
39             scalar => ['driver'],
40             scalar => [{-default => ''}, 'driverDetail'],
41             scalar => ['server'],
42             scalar => ['database'],
43             scalar => ['user'],
44             scalar => ['passwd'],
45             scalar => [{-default => 1}, 'db_AutoCommit'],
46             scalar => [{-default => 0}, 'db_RaiseError'],
47             scalar => [{-default => 102400}, 'maxReadLen'],
48             hash => ['connectAttrs'],
49             array => ['tables'],
50             hash => ['table_infos'],
51             scalar => ['mutexName', 'mutex'],
52 0         0 scalar => [{-default => sub { my ($self, $attempts, $gotit)=@_;
53 0 0       0 if ( !$gotit ) {
54              
55 0 0       0 if ( $attempts % 100 == 0 ) {
    0          
56 0         0 warn("waiting on db lock.. ($attempts attempts)");
57 0         0 Time::HiRes::usleep(5*1000*1000);
58             }
59             elsif ( $attempts % 10 == 0 ) {
60 0         0 warn("waiting on db lock.. ($attempts attempts)");
61 0         0 Time::HiRes::usleep(1*1000*1000);
62             }
63             else {
64 0         0 Time::HiRes::usleep(100*1000);
65             }
66             }
67 0         0 return(0);
68 1         33 }}, 'mutexWaitCallback'],
69             new => [qw/ -hash new /] ,
70 1     1   6 ];
  1         2  
71              
72              
73             sub DESTROY($)
74             {
75 0     0     my ($self)=@_;
76              
77 0 0         if ( $self->isConnected() ) {
78 0           $self->disconnect();
79             }
80             }
81              
82             =head2 connect
83              
84             =cut
85              
86             sub connect($)
87             {
88 0     0 1   my ($self)=@_;
89              
90 0           $self->disconnect();
91 0 0         return $self if ( !defined($self->driver()) );
92              
93 0           my $dsn='dbi:'.$self->driver;
94              
95 0 0         if ( defined($self->driverDetail()) ) {
96 0           $dsn.=":".$self->driverDetail();
97             }
98 0 0 0       if ( defined($self->server()) && length($self->server()) ) {
99 0 0         if ( $self->driver eq 'ODBC' ) {
    0          
    0          
100 0           $dsn.=";Server=".$self->server();
101             }
102             elsif ( $self->driver eq 'SQLite' ) {
103             # remote server not supported
104             }
105             elsif ( $self->driver eq 'mysql' ) {
106 0           $dsn.=":host=".$self->server();
107             }
108             }
109 0 0 0       if ( defined($self->database()) && length($self->database()) ) {
110 0 0         if ( $self->driver eq 'ODBC' ) {
    0          
111 0           $dsn.=";Database=".$self->database();
112             }
113             elsif ( $self->driver eq 'SQLite' ) {
114 0           $dsn.=":dbname=".$self->database();
115             }
116             else {
117 0           $dsn.=":database=".$self->database();
118             }
119             }
120              
121 0 0 0       if ( defined($self->mutexName) && length($self->mutexName) ) {
122 0 0         if ( $^O eq 'MSWin32' ) {
    0          
123 0           require Win32::Mutex;
124              
125 0           my $attempts=0;
126 0           while ( 1 ) {
127 0           my $name="Global\\".$self->mutexName;
128 0 0 0       if ( defined($self->database()) && length($self->database()) ) {
129 0           $name="Global\\".$self->database()."-".$self->mutexName();
130             }
131 0           my $mutex=Win32::Mutex->new(1, $name);
132 0 0         if ( $mutex ) {
133 0 0         if ( $^E == 183 ) {
134 0           undef($mutex);
135 0           my $sub=$self->mutexWaitCallback();
136 0           my $stop=&$sub($self, ++$attempts, 0);
137 0 0         if ( $stop ) {
138 0           return(undef);
139             }
140             }
141             else {
142 0           $self->mutex($mutex);
143 0 0         if ( $attempts != 0 ) {
144 0           my $sub=$self->mutexWaitCallback();
145 0           &$sub($self, $attempts, 1);
146             }
147 0           last;
148             }
149             }
150             }
151             #warn("mutex is MINE");
152             }
153             elsif ( $^O eq 'linux' ) {
154 1     1   37670 use Fcntl ':flock';
  1         1  
  1         3730  
155 0           my $attempts=0;
156 0           while ( 1 ) {
157 0           my $name="/tmp/.".$self->mutexName();
158 0 0 0       if ( defined($self->database()) && length($self->database()) ) {
159 0           $name=$self->database()."-".$self->mutexName().".lck";
160             }
161              
162 0           my $fd;
163              
164 0 0         if ( !open($fd, "> $name") ) {
    0          
165 0           warn("$name:$!");
166              
167             # die "Can't open $name for locking!\nError: $!\n";
168 0           my $sub=$self->mutexWaitCallback();
169 0           my $stop=&$sub($self, ++$attempts, 0);
170 0 0         if ( $stop ) {
171 0           return(undef);
172             }
173             }
174             elsif ( !flock($fd, LOCK_EX|LOCK_NB) ) {
175             #warn("$name:$!");
176 0           my $sub=$self->mutexWaitCallback();
177 0           my $stop=&$sub($self, ++$attempts, 0);
178 0           close($fd);
179 0 0         if ( $stop ) {
180 0           return(undef);
181             }
182             # spin and retry
183             }
184             else {
185 0           $self->mutex($fd);
186 0 0         if ( $attempts != 0 ) {
187 0           my $sub=$self->mutexWaitCallback();
188 0           &$sub($self, $attempts, 1);
189             }
190 0           last;
191             }
192             }
193             }
194             }
195              
196             #warn("DB: $dsn\n");
197 0           my %attrs=(RaiseError=>$self->db_RaiseError,
198             AutoCommit=>$self->db_AutoCommit);
199              
200 0           for my $k ($self->connectAttrs_keys()) {
201 0           $attrs{$k}=$self->connectAttrs_index($k);
202             }
203            
204 0           my $dbh=DBI->connect($dsn, $self->user, $self->passwd, \%attrs);
205            
206 0 0         if ( !$dbh ) {
207 0           print STDERR "connection failed:$DBI::errstr\n";
208 0 0         if ( defined($self->mutex) ) {
209 0 0         if ( $^O eq 'MSWin32' ) {
    0          
210 0           $self->mutex()->release();
211             }
212             elsif ( $^O eq 'linux' ) {
213 0           close $self->mutex();
214             }
215             }
216 0           $self->mutex(undef);
217 0           return(undef);
218             }
219              
220             #print STDERR "autocommit: ".$dbh->{AutoCommit}."\n";
221              
222             # default for ODC is 80 bytes for LongReadLen, which causes
223             # ODBC SQL Server Driver String data, right truncation (SQL-01004)
224             # failures for things with lasrge enough strings
225             #
226             # note: setting arbitrarily high # causes memory allocation problems.
227 0           $dbh->{LongReadLen} = $self->maxReadLen();
228 0           $dbh->{LongTruncOk} = 0;
229              
230 0 0         if ($dbh->err()) {
231 0           warn($dbh->errstr()."\n");
232 0           $dbh->disconnect();
233 0 0         if ( $^O eq 'MSWin32' ) {
    0          
234 0           $self->mutex()->release();
235             }
236             elsif ( $^O eq 'linux' ) {
237 0           close $self->mutex();
238             }
239 0           $self->mutex(undef);
240 0           return(undef);
241             }
242 0           $self->dbh($dbh);
243              
244 0 0         if ( $self->driver eq 'SQLite' ) {
245             #$self->runSQL("PRAGMA cache_size=200000");
246             }
247 0           $self->modCount(0);
248 0           return($self);
249             }
250              
251             =head2 disconnect
252              
253             Disconnect from the database. Note that for those lazy programmers that fail to call disconnect, the disconnect will be called when the
254             object is destructed through perl's DESTROY.
255              
256             =cut
257              
258             sub disconnect()
259             {
260 0     0 1   my ($self)=@_;
261              
262 0 0         if ( $self->dbh() ) {
263 0           $self->dbh->disconnect();
264 0           $self->dbh(undef);
265             }
266              
267 0 0         if ( defined($self->mutex) ) {
268 0 0         if ( $^O eq 'MSWin32' ) {
    0          
269 0           $self->mutex()->release();
270             }
271             elsif ( $^O eq 'linux' ) {
272 0           close $self->mutex();
273             }
274             #warn("mutex is NOT-MINE");
275 0           $self->mutex(undef);
276             }
277 0           $self->tables_reset();
278             }
279              
280             =head2 commit
281              
282             Commit a DBI transaction (should only be used if db_AutoCommit was zero).
283              
284             =cut
285              
286             sub commit()
287             {
288 0     0 1   my ($self)=@_;
289              
290 0 0         if ( ! defined($self->dbh) ) {
291 0           carp("not connected");
292             }
293 0           $self->dbh->commit();
294 0           $self->modCount(0);
295             }
296              
297             =head2 isConnected
298              
299             Check to see if there has been previous successful 'connect' call.
300              
301             =cut
302              
303             sub isConnected($)
304             {
305 0     0 1   return(defined(shift->dbh));
306             }
307              
308             =head2 quote
309              
310             =cut
311              
312             sub quote($)
313             {
314 0     0 1   my ($self, @rest)=@_;
315              
316 0 0         if ( ! defined($self->dbh) ) {
317 0           carp("not connected");
318             }
319 0           return($self->dbh->quote(@rest));
320             }
321              
322             =head2 last_inserted_key
323              
324             Retrieve the last inserted key for a given table and primaryKey.
325              
326             =cut
327              
328             sub last_inserted_key($$$)
329             {
330 0     0 1   my ($self, $table, $primaryKey)=@_;
331              
332 0 0         if ( ! defined($self->dbh) ) {
333 0           carp("not connected");
334             }
335              
336 0 0         if ( $self->driver eq 'ODBC' ) {
337 0           return $self->select2Scalar("select \@\@identity");
338             }
339 0           return $self->dbh->last_insert_id($self->database, undef, $table, $primaryKey);
340             }
341              
342             =head2 runSQL
343              
344             Execute a sql statement and return 1 upon success and 0 upon success. Upon failure, carp() is called with the sql statement.
345              
346             =cut
347              
348             sub runSQL($)
349             {
350 0     0 1   my ($self, $stmt)=@_;
351              
352 0 0         if ( ! defined($self->dbh) ) {
353 0           carp("not connected");
354             }
355              
356 0           my $dbh=$self->dbh();
357              
358             #warn("$stmt");
359 0           $dbh->do($stmt);
360 0 0         if ( $dbh->err() ) {
361 0           carp($stmt);
362 0           return(0);
363             }
364 0           return(1);
365             }
366              
367             =head2 runSQL_err
368              
369             Return DBI->err() to retrieve error status of previous call to runSQL.
370              
371             =cut
372              
373             sub runSQL_err($)
374             {
375 0     0 1   my ($self)=@_;
376              
377 0 0         if ( ! defined($self->dbh) ) {
378 0           carp("not connected");
379             }
380              
381 0           return $self->dbh()->err();
382             }
383              
384             =head2 runSQL_srrstr
385              
386             Return DBI->errstr() to retrieve error status of previous call to runSQL.
387              
388             =cut
389              
390             sub runSQL_errstr($)
391             {
392 0     0 0   my ($self)=@_;
393              
394 0 0         if ( ! defined($self->dbh) ) {
395 0           carp("not connected");
396             }
397              
398 0           return $self->dbh()->errstr();
399             }
400              
401             =head2 prepare
402              
403             Return DBI->prepare() for a given statement.
404              
405             =cut
406              
407             sub prepare($$)
408             {
409 0     0 1   my ($self, $query)=@_;
410              
411 0 0         if ( !defined($self->dbh) ) {
412 0           carp("not connected");
413             }
414             #warn("STMT:$query");
415 0           return $self->dbh->prepare($query);
416             }
417              
418             =head2 execute
419              
420             Wrapper for calling DBI->prepare() and DBI->exeute() fora given query. Upon success the DBI->prepare() handle is returned.
421             Upon failure, warn() is called with the query statement string and undef is returned.
422              
423             =cut
424              
425             sub execute($)
426             {
427 0     0 1   my ($self, $query)=@_;
428              
429 0 0         if ( !defined($self->dbh) ) {
430 0           carp("not connected");
431             }
432              
433             #warn("STMT:$query");
434 0           my $sth = $self->prepare($query);
435 0 0         if ( !$sth ) {
436 0           warn("STMT INVALID:$query");
437 0           return(undef);
438             }
439 0 0         if ( !$sth->execute() ) {
440 0           warn("FAILED STMT:$query");
441 0           return(undef);
442             }
443 0           return($sth);
444             }
445              
446             =head2 insert_row
447              
448             Execute a table insertion and return the created primaryKey (if specified).
449             If primaryKey is not defined, 1 is returned upon success.
450              
451             =cut
452              
453             sub insert_row($$$%)
454             {
455 0     0 1   my ($self, $table, $primaryKey, %args) = @_;
456              
457 0           my $dbh=$self->dbh();
458 0 0         if ( !defined($dbh) ) {
459 0           carp("not connected");
460             }
461              
462 0           my ($k,$v)=('','');
463 0           my @values;
464              
465 0           for my $key (sort keys %args) {
466 0 0         if ( defined($args{$key}) ) {
467 0           $k.=$key.",";
468             #$k.=$self->dbh->quote($key).",";
469 0           push(@values, $args{$key});
470             #$v.=$self->dbh->quote($args{$key}).",";
471 0           $v.="?,";
472             }
473             }
474              
475 0 0 0       if ( !($k=~s/,$//) || !($v=~s/,$//) ) {
476 0           warn "attempt to insert nothing into table $table";
477 0           return(undef);
478             }
479              
480 0           my $stmt="INSERT INTO $table ($k) VALUES ($v)";
481             #print STDERR "STMT:$stmt\n";
482             #print STDERR "VALUES:(".join(',', map($self->dbh->quote($_), @values)).")\n";
483            
484 0           my $sth=$dbh->prepare($stmt);
485 0           $sth->execute(@values);
486              
487 0 0         if ($dbh->err()) {
488 0           print STDERR "Error inserting into table: $table: ", $dbh->errstr()."\n";
489 0           print STDERR "STMT:$stmt\n";
490             #print STDERR "VALUES:$stmt\n";
491 0           return undef;
492             }
493 0           $self->modCount($self->modCount()+1);
494              
495             # return primary key if its specified
496 0 0         if ( !defined($primaryKey) ) {
497 0           return(1);
498             }
499 0           return $self->last_inserted_key($table, $primaryKey);
500             }
501              
502             sub _quoteField($$)
503             {
504 0     0     my ($self, $word)=@_;
505              
506 0 0         if ( $self->driver eq 'ODBC' ) {
507             # check for keywords used in column names we need to quote
508 0 0         if ( $word eq 'File' ) {
509 0           return('['.$word.']');
510             }
511              
512             # catch table.column in select
513 0 0         if ( $word=~m/^([^\.]+)\.(File)$/o ) {
514 0           return($1.'['.$2.']');
515             }
516             }
517              
518             # no quote needed
519 0           return($word);
520             }
521              
522             =head2 query2SQLStatement
523              
524             Construct an sql query using a hash containing:
525              
526             fields - required array of fields to select
527             tables - required array of tables to select from
528             wheres - optional array of where clauses to include (all and'd together)
529             groupbys - optional array of group by clauses to include
530             sortByField - optional field to sort by (if prefixed with -, then sort is reversed)
531             orderbys - optional array of order by clauses to include
532             offset - offset of returned rows
533             limit - optional integer value to limit # of returned rows
534              
535             =cut
536              
537             sub query2SQLStatement($%)
538             {
539 0     0 1   my ($self, %args)=@_;
540            
541             #print Dumper(\%args);
542 0           my @columnHeaders=$args{fields};
543            
544 0           my $st="SELECT ";
545 0 0 0       if ( $args{limit} && $self->driver eq 'ODBC' ) {
546             # mssql
547 0           $st.="TOP $args{limit} ";
548             }
549 0           my $cnt=0;
550 0           for (@{$args{fields}}) {
  0            
551 0           $st.=$self->_quoteField($_).",";
552 0           $cnt++;
553             }
554 0 0         $st=~s/,$// || die "no fields in select";
555              
556 0           $st.=" FROM ";
557 0           for (@{$args{tables}}) {
  0            
558 0           $st.=$_.",";
559             }
560 0 0         $st=~s/,$// || die "no tables specified";
561              
562 0 0         if ( $args{wheres} ) {
563 0           my $w=" WHERE ";
564 0           for (@{$args{wheres}}) {
  0            
565 0           $w.="$_ AND ";
566             }
567 0 0         $st.=$w if ( $w=~s/ AND $// );
568             }
569              
570 0 0         if ( $args{groupbys} ) {
571 0           my $w=" GROUP BY ";
572 0           for (@{$args{groupbys}}) {
  0            
573 0           $w.=$self->_quoteField($_).",";
574             }
575 0 0         $st.=$w if ( $w=~s/,$// );
576             }
577              
578 0           my $sortedField='';
579 0           my $sortedDescending=0;
580 0 0 0       if ( defined($args{sortByField}) && length($args{sortByField})) {
  0 0 0        
581 0           $sortedField=$args{sortByField};
582 0 0         if ( $sortedField=~s/^\-// ) {
583 0           $st.=" ORDER BY ".$self->_quoteField($sortedField)." DESC";
584 0           $sortedDescending=1;
585             }
586             else {
587 0           $st.=" ORDER BY ".$self->_quoteField($sortedField)." ASC";
588 0           $sortedDescending=0;
589             }
590             }
591             elsif ( defined($args{orderbys}) && @{$args{orderbys}} ) {
592 0           $st.=" ORDER BY ";
593 0           for (@{$args{orderbys}}) {
  0            
594 0           $st.=$self->_quoteField($_).",";
595             }
596 0 0         $st=~s/,$// || die "no orderbys specified";
597             }
598              
599 0 0         if ( $args{limit} ) {
600 0 0         if ( $self->driver eq 'ODBC' ) {
    0          
    0          
601             # covered at top of this sub
602             }
603             elsif ( $self->driver eq 'mysql' ) {
604 0           $st.=" LIMIT ".$args{limit};
605             }
606             elsif ( $self->driver eq 'SQLite' ) {
607 0           $st.=" LIMIT ".$args{limit};
608             }
609              
610 0 0         if ( $args{offset} ) {
611 0 0         if ( $args{offset} < 0 ) {
612 0           $args{offset}=0;
613             }
614 0           $st.=" OFFSET ".$args{offset};
615             }
616             else {
617 0           $args{offset}=0;
618             }
619             }
620 0           return($st);
621             }
622              
623             =head2 findRecords
624              
625             Call query2SQLStatement with the given hash arguments and return a IMDB::Local::DB::RecordIterator handle.
626              
627             In addition to the query2SQLStatement arguments the following are optional:
628             cacheBy - set cacheBy value in returned IMDB::Local::DB::RecordIterator handle. If not specified, limit is used.
629              
630             =cut
631              
632             sub findRecords($%)
633             {
634 0     0 1   my ($self, %args)=@_;
635              
636 0           my $cacheBy=1000;
637 0 0         if ( $args{cacheBy} ) {
638 0           $cacheBy=delete($args{cacheBy});
639             }
640 0 0 0       if ( $args{limit} && $cacheBy > $args{limit} ) {
641 0           $cacheBy=$args{limit};
642             }
643 0           my $st=$self->query2SQLStatement(%args);
644              
645             #print STDERR "running '$st'\n";
646 0           my $sth=$self->execute($st);
647 0 0         if ( !defined($sth) ) {
648 0           warn("sql '$st' failed:".$self->dbh->errstr()."\n");
649 0           return(undef);
650             }
651              
652 0 0         if ($self->dbh->err()) {
653 0           print STDERR "Query error: ", $self->dbh->errstr()."\n";
654 0           print STDERR "STMT:$st\n";
655             #print STDERR "VALUES:$stmt\n";
656 0           return undef;
657             }
658              
659 0           my $int=new IMDB::Local::DB::RecordIterator($sth);
660 0           $int->{cacheBy}=$cacheBy;
661 0           return($int);
662             }
663              
664             =head2 rowExists
665              
666             Check to see at least one row exists with value in 'column' in the specified 'table'
667              
668             =cut
669              
670             sub rowExists($$$)
671             {
672 0     0 1   my ($self, $table, $column, $value)=@_;
673 0           my $sql;
674 0 0         if ( $self->driver eq 'ODBC' ) {
    0          
    0          
675 0           $sql="SELECT TOP 1 $column from $table where $column='$value'";
676             }
677             elsif ( $self->driver eq 'mysql' ) {
678 0           $sql="SELECT $column from $table where $column='$value' LIMIT 1";
679             }
680             elsif ( $self->driver eq 'SQLite' ) {
681 0           $sql="SELECT $column from $table where $column='$value' LIMIT 1";
682             }
683              
684 0           my $v=$self->select2Scalar($sql);
685 0   0       return(defined($v) && $v eq $value);
686             }
687              
688             =head2 select2Scalar
689              
690             Execute the given sql statement and return the value in a single scalar value
691              
692             =cut
693              
694             sub select2Scalar($$)
695             {
696 0     0 1   my ($self, $sql)=@_;
697              
698 0           my $sth=$self->execute($sql);
699 0 0         if ( !$sth ) {
700 0           return(undef);
701             }
702 0           my @arr=$sth->fetchrow_array;
703 0           return $arr[0];
704             }
705              
706             =head2 select2Int
707              
708             Execute the given sql statement and return the value cast as an integer (ie int(returnvalue))
709              
710             =cut
711              
712             sub select2Int($$)
713             {
714 0     0 1   my ($self, $sql)=@_;
715              
716 0           my $r=$self->select2Scalar($sql);
717 0 0         if ( defined($r) ) {
718 0           $r=int($r);
719             }
720 0           return $r;
721             }
722              
723             =head2 select2Array
724              
725             Execute the given sql statement and return an array with all the results.
726              
727             =cut
728              
729             sub select2Array($$)
730             {
731 0     0 1   my ($self, $sql)=@_;
732              
733 0           my $sth=$self->execute($sql);
734 0 0         if ( !$sth ) {
735 0           return(undef);
736             }
737 0           my @arr;
738 0           my $all=$sth->fetchall_arrayref();
739 0           for my $refer (@$all) {
740 0           push(@arr, @$refer);
741             }
742 0           return \@arr;
743             }
744              
745             =head2 select2Matrix
746              
747             Execute the given sql statement and return an array of arrays, each containing a row of values
748              
749             =cut
750              
751             sub select2Matrix($$)
752             {
753 0     0 1   my ($self, $sql)=@_;
754              
755 0           my $sth=$self->execute($sql);
756 0 0         if ( !$sth ) {
757 0           return(undef);
758             }
759 0           my $int=new IMDB::Local::DB::RecordIterator($sth);
760 0           my @arr;
761 0           while (my $refer=$int->nextRow()) {
762 0           push(@arr, $refer);
763             }
764 0           return(\@arr);
765             }
766              
767             =head2 select2HashRef
768              
769             Execute the given sql statement and return a reference to a hash with the result
770              
771             =cut
772              
773             sub select2HashRef($$)
774             {
775 0     0 1   my ($self, $sql)=@_;
776              
777 0           my $sth=$self->execute($sql);
778 0 0         if ( !$sth ) {
779 0           return(undef);
780             }
781 0           return($sth->fetchrow_hashref());
782             }
783              
784             =head2 select2Hash
785              
786             Execute the given sql statement and return a reference ot a hash containing the given row.
787              
788             =cut
789              
790             sub select2Hash($$)
791             {
792 0     0 1   my ($self, $sql)=@_;
793              
794 0           my $sth=$self->execute($sql);
795 0 0         if ( !$sth ) {
796 0           return(undef);
797             }
798 0           my $int=new IMDB::Local::DB::RecordIterator($sth);
799 0           my %arr;
800 0           while (my $refer=$int->nextRow()) {
801 0           my $key=$refer->[0];
802 0           my @r=splice(@$refer, 1);
803 0 0         if ( scalar(@r) == 1 ) {
804 0           $arr{$key}=$r[0];
805             }
806             else {
807 0           $arr{$key}=\@r;
808             }
809             }
810 0           return(\%arr);
811             }
812              
813             sub _NOT_USED_database_list($)
814             {
815 0     0     my $self=shift;
816              
817 0 0         if ( $self->driver eq 'ODBC' ) {
    0          
818 0           my @list;
819 0           for my $t (@{$self->select2Array("select Name from master..sysdatabases")} ) {
  0            
820 0           push(@list, $t);
821             }
822 0           return(@list);
823             }
824             elsif ( $self->driver eq 'SQLite' ) {
825 0           warn "database_list of sqlite db: unsupported";
826 0           return(undef);
827             }
828             else {
829             # mysql
830 0           my @list;
831 0           for my $t (@{$self->select2Array("show databases")} ) {
  0            
832 0           push(@list, $t);
833             }
834 0           return(@list);
835             }
836             }
837              
838             =head2 table_list
839              
840             Retrieve a list of tables available. Created Tables or Views created after connect() may not be included.
841              
842             =cut
843              
844             sub table_list($)
845             {
846 0     0 1   my $self=shift;
847              
848             # already got an answer ?
849 0 0         if ( $self->tables_index(0) ) {
850 0           return($self->tables);
851             }
852              
853 0 0         if ( $self->driver eq 'ODBC' ) {
    0          
854 0           my $sth=$self->dbh()->table_info($self->database, '', '', 'TABLE');
855 0 0         if ( !defined($sth) ) {
856 0           warn("lookup of table_info failed:".$self->dbh->errstr()."\n");
857 0           return($self->tables);
858             }
859 0           my $int=new IMDB::Local::DB::RecordIterator($sth);
860 0           while (my $refer=$int->nextRow()) {
861 0           my ($table_cat, $table_schem, $table_name, $table_type, $remarks)=@$refer;
862 0           $self->tables_push($table_name);
863             }
864             #warn "tables:".join(',', $self->tables)."\n";
865 0           return($self->tables);
866             }
867             elsif ( $self->driver eq 'SQLite' ) {
868             #$sth=$self->dbh()->table_info('%', '%', '%', 'TABLE');
869 0           for my $t (@{$self->select2Array("select Name from sqlite_master where type='table'")} ) {
  0            
870 0           $self->tables_push($t);
871             }
872 0           return($self->tables);
873             }
874             else {
875             # mysql
876 0           for my $t (@{$self->select2Array("show tables")} ) {
  0            
877 0           $self->tables_push($t);
878             }
879 0           return($self->tables);
880             }
881            
882             }
883              
884             =head2 table_exists
885              
886             Check to see if a given table exists. Uses table_list.
887              
888             =cut
889              
890             sub table_exists($$)
891             {
892 0     0 1   my ($dbh, $table)=@_;
893              
894 0 0         if ( !grep(/^$table$/, $dbh->table_list()) ) {
895 0           return(0);
896             }
897 0           return(1);
898             }
899              
900             =head2 table_exists
901              
902             Check to see if a given table exists. Uses table_list.
903              
904             =cut
905              
906             sub table_clear($$)
907             {
908 0     0 0   my ($db, $table)=@_;
909              
910 0 0         if ( !$db->isConnected() ) {
911 0           carp("not connected");
912             }
913 0 0         if ( $db->table_exists($table) ) {
914 0 0         if ( $db->runSQL("DELETE from $table") ) {
915 0 0 0       if ( $db->driver eq 'SQLite' &&
916             # reset state related to table (ie automatic id counters)
917             $db->runSQL("DELETE from sqlite_sequence where name='$table'") ) {
918             }
919 0           $db->commit();
920 0           return(1);
921             }
922             }
923 0           return(0);
924             }
925              
926             =head2 column_info
927              
928             Retrieve information about a given column in a table. Changes to columns made after connect() may not be included.
929              
930             Returns a list of columns in a database/driver specific order containing:
931             COLUMN_NAME - name of the column
932             TYPE_NAME - data type (if available)
933             COLUMN_SIZE - size of column data (if available)
934             IS_NULL - true/false if column is nullable
935             IS_PRIMARY_KEY - 1 or 0 if column is a primary key
936              
937             =cut
938              
939             sub column_info($$)
940             {
941 0     0 1   my ($self, $table)=@_;
942              
943 0 0         if ( $self->table_infos_exists($table) ) {
944 0           return $self->table_infos_index($table);
945             }
946              
947 0           my @key_column_names;
948 0 0         if ( $self->driver eq 'ODBC' ) {
    0          
    0          
949 0           @key_column_names = $self->dbh()->primary_key($self->database, '', $table);
950             }
951             elsif ( $self->driver eq 'SQLite' ) {
952             # untested
953 0           @key_column_names = $self->dbh()->primary_key(undef, undef, $table);
954             }
955             elsif ( $self->driver eq 'mysql' ) {
956 0           my @list;
957 0           my $res=$self->select2Matrix("describe $table");
958 0 0         if ( $res ) {
959 0           for my $h (@$res) {
960 0           my ($field, $type,$null, $key,$default,$extra)=@$h;
961 0 0         $default='' if (!defined($default));
962 0           my $t;
963 0           $t->{COLUMN_NAME}=$field;
964 0           $t->{TYPE_NAME}=$type;
965 0           $t->{COLUMN_SIZE}=0;
966 0 0         if ( $t->{TYPE_NAME}=~s/\((\d+)\)$// ) {
967 0           $t->{COLUMN_SIZE}=$1;
968             }
969 0           $t->{IS_NULLABLE}=uc($null);
970 0 0         $t->{IS_PRIMARY_KEY}=(uc($key) eq 'PRI')?1:0;
971 0           push(@list, $t);
972             }
973             }
974 0 0         if ( !@list ) {
975 0           warn("no table information for table '$table'");
976 0           return(undef);
977             }
978 0           @list=sort {$a->{COLUMN_NAME} cmp $b->{COLUMN_NAME}} @list;
  0            
979 0           $self->table_infos($table, \@list);
980 0           return $self->table_infos_index($table);
981             }
982             else {
983 0           die "unsupported";
984             }
985            
986 0           my $sth;
987 0 0         if ( $self->driver eq 'ODBC' ) {
    0          
988 0           $sth=$self->dbh()->column_info($self->database, '', $table, '%');
989             }
990             elsif ( $self->driver eq 'SQLite' ) {
991             # untested
992 0           $sth=$self->dbh()->column_info(undef, '%', $table, '%');
993             }
994             else {
995 0           $sth=$self->dbh()->column_info(undef, '%', $table, '%');
996             }
997 0 0         if ( !defined($sth) ) {
998 0           warn("no table information for table '$table'");
999 0           return(undef);
1000             }
1001            
1002 0           my @list;
1003 0           while (my $hash=$sth->fetchrow_hashref()) {
1004 0 0         if ( @key_column_names ) {
1005 0           my $col=$hash->{COLUMN_NAME};
1006 0 0         if ( grep/^$col$/, @key_column_names ) {
1007 0           $hash->{IS_PRIMARY_KEY}=1;
1008             }
1009             else {
1010 0           $hash->{IS_PRIMARY_KEY}=0;
1011             }
1012             }
1013 0           push(@list, $hash);
1014             }
1015              
1016 0 0         if ( !@list ) {
1017 0           warn("no table information for table '$table'");
1018 0           return(undef);
1019             }
1020              
1021 0           $self->table_infos($table, \@list);
1022 0           return $self->table_infos_index($table);
1023             }
1024              
1025             =head2 column_list
1026              
1027             Retrieve a list of column names in column_list order.
1028              
1029             =cut
1030              
1031             sub column_list($$)
1032             {
1033 0     0 1   my ($self, $table)=@_;
1034              
1035 0           my @columns;
1036 0           for my $t (@{$self->column_info($table)}) {
  0            
1037 0           push(@columns, $t->{COLUMN_NAME});
1038             }
1039 0           return(@columns);
1040             }
1041              
1042             =head2 writeQuery2CSV
1043              
1044             Run an sql query and output the result to the specified file in Text::CSV format
1045              
1046             =cut
1047              
1048             sub writeQuery2CSV($$$)
1049             {
1050 0     0 1   my ($self, $file, $hash)=@_;
1051 0           require Text::CSV;
1052              
1053 0           my $csv=new Text::CSV({binary=>1, always_quote=>1});
1054              
1055 0           my $int=$self->findRecords(%$hash);
1056 0 0         if ($int) {
1057 0 0         if ( open(my $fd, "> $file") ) {
1058 0           $csv->combine(@{$hash->{fields}});
  0            
1059 0           print $fd $csv->string()."\n";
1060            
1061 0           while (my $refer=$int->nextRow()) {
1062 0           $csv->combine(@$refer);
1063 0           print $fd $csv->string()."\n";
1064             }
1065 0           close($fd);
1066 0           return(1);
1067             }
1068             }
1069 0           return(0);
1070             }
1071              
1072             =head2 appendCSV2Table
1073              
1074             Parse the given CVS file (which must have column names that match a the given table) and insert each row
1075             as a new row into the specified table.
1076              
1077             Upon success, returns > 0, number of rows successfully inserted.
1078             Returns 0 if open() on the given file fails.
1079              
1080             =cut
1081              
1082             sub appendCSV2Table($$$)
1083             {
1084 0     0 1   my ($self, $file, $table)=@_;
1085 0           require Text::CSV;
1086              
1087 0           my $csv=new Text::CSV({binary=>1, always_quote=>1});
1088              
1089 0 0         if ( open(my $fd, "<:encoding(utf8)", $file) ) {
1090 0           my $lineNum=1;
1091 0           my @titleRow=@{$csv->getline($fd)};
  0            
1092 0           my $rowsInserted=0;
1093            
1094 0           while ( my $row = $csv->getline($fd) ) {
1095 0           $lineNum++;
1096 0 0         if ( scalar(@titleRow) != scalar(@$row) ) {
1097 0           warn("$file: invalid row: $lineNum\n");
1098             }
1099             else {
1100 0           my %args;
1101 0           my $c=0;
1102 0           for my $t (@titleRow) {
1103 0           $args{$t}=$row->[$c];
1104 0           $c++;
1105             }
1106 0 0         if ( $self->insert_row($table, undef, %args) ) {
1107 0           $rowsInserted++;
1108             }
1109             }
1110             }
1111            
1112 0 0         $csv->eof or $csv->error_diag();
1113 0           close($fd);
1114 0           $self->commit();
1115 0           return($rowsInserted);
1116             }
1117 0           return(0);
1118             }
1119              
1120             =head2 table_row_count
1121              
1122             Retrieve the # of rows in a given table.
1123              
1124             =cut
1125              
1126             sub table_row_count($$)
1127             {
1128 0     0 1   my ($self, $table)=@_;
1129              
1130 0 0         if ( $self->driver eq 'mysql' ) {
    0          
1131 0           return $self->select2Int("select count(*) from $table");
1132             }
1133             elsif ( $self->driver eq 'SQLite' ) {
1134 0           return $self->select2Int("select count(1) from $table");
1135             }
1136             else {
1137             # mssql magic
1138 0           my $sql="sp_Msforeachtable 'sp_spaceused ''$table'''";
1139 0           my $sth=$self->execute($sql);
1140 0 0         if ( !defined($sth) ) {
1141 0           carp("table_row_count: called with non-existing table $table");
1142 0           return(0);
1143             }
1144            
1145 0           my $int=new IMDB::Local::DB::RecordIterator($sth);
1146 0           my @infos;
1147 0           while (my $r=$int->nextRow()) {
1148 0           my ($table, $rows, $reserved_kb, $data_kb, $index_kb, $unused_kb)=@$r;
1149 0           return (int($rows));
1150             }
1151             #return $self->select2Scalar("select count(1) from $table");
1152             }
1153             }
1154              
1155             =head2 table_report
1156              
1157             Retrieve a reference to an array of arrays, each sub-array containing [table, #ofRows, data-size-in-KBs, index-size-in-KBs]
1158              
1159             =cut
1160              
1161             sub table_report($@)
1162             {
1163 0     0 1   my ($self, @tables)=@_;
1164              
1165 0 0         if ( $self->driver eq 'mysql' ) {
    0          
1166 0 0         if ( !@tables ) {
1167 0           @tables=$self->table_list();
1168             }
1169 0           my @infos;
1170 0           for my $table (@tables) {
1171 0           my $rows=$self->select2Array("select count(*) from $table");
1172 0           push(@infos, [$table, $rows->[0], 0, 0]);
1173             }
1174 0           return(\@infos);
1175             }
1176             elsif ( $self->driver eq 'SQLite' ) {
1177 0 0         if ( !@tables ) {
1178 0           @tables=$self->table_list();
1179             }
1180 0           my @infos;
1181 0           for my $table (@tables) {
1182 0           my $rows=$self->select2Array("select count(*) from $table");
1183 0           push(@infos, [$table, $rows->[0], 0, 0]);
1184             }
1185 0           return(\@infos);
1186             }
1187             else {
1188             # fall-through
1189             }
1190              
1191             # mssql magic
1192 0           my $sql="sp_Msforeachtable 'sp_spaceused ''?'''";
1193 0           my $sth=$self->execute($sql);
1194            
1195 0           my $int=new IMDB::Local::DB::RecordIterator($sth);
1196 0           my @infos;
1197 0           while (my $r=$int->nextRow()) {
1198 0           my ($table, $rows, $reserved_kb, $data_kb, $index_kb, $unused_kb)=@$r;
1199 0 0 0       if ( !@tables || grep(/^$table$/, @tables) ) {
1200              
1201             # knock off units
1202 0           $data_kb=~s/\s*KB$//o;
1203 0           $index_kb=~s/\s*KB$//o;
1204 0           $unused_kb=~s/\s*KB$//o;
1205              
1206 0           $rows=~s/^\s*//o;
1207 0           $rows=~s/\s*$//o;
1208 0           push(@infos, [$table, $rows, $data_kb, $index_kb]);
1209             }
1210             }
1211 0           return(\@infos);
1212             }
1213            
1214             =head1 AUTHOR
1215              
1216             jerryv, C<< >>
1217              
1218             =head1 BUGS
1219              
1220             Please report any bugs or feature requests to C, or through
1221             the web interface at L. I will be notified, and then you'll
1222             automatically be notified of progress on your bug as I make changes.
1223              
1224             =head1 SUPPORT
1225              
1226             You can find documentation for this module with the perldoc command.
1227              
1228             perldoc IMDB::Local::DB::Base
1229              
1230              
1231             You can also look for information at:
1232              
1233             =over 4
1234              
1235             =item * RT: CPAN's request tracker (report bugs here)
1236              
1237             L
1238              
1239             =item * AnnoCPAN: Annotated CPAN documentation
1240              
1241             L
1242              
1243             =item * CPAN Ratings
1244              
1245             L
1246              
1247             =item * Search CPAN
1248              
1249             L
1250              
1251             =back
1252              
1253              
1254             =head1 ACKNOWLEDGEMENTS
1255              
1256              
1257             =head1 LICENSE AND COPYRIGHT
1258              
1259             Copyright 2015 jerryv.
1260              
1261             This program is free software; you can redistribute it and/or modify it
1262             under the terms of the the Artistic License (2.0). You may obtain a
1263             copy of the full license at:
1264              
1265             L
1266              
1267             Any use, modification, and distribution of the Standard or Modified
1268             Versions is governed by this Artistic License. By using, modifying or
1269             distributing the Package, you accept this license. Do not use, modify,
1270             or distribute the Package, if you do not accept this license.
1271              
1272             If your Modified Version has been derived from a Modified Version made
1273             by someone other than you, you are nevertheless required to ensure that
1274             your Modified Version complies with the requirements of this license.
1275              
1276             This license does not grant you the right to use any trademark, service
1277             mark, tradename, or logo of the Copyright Holder.
1278              
1279             This license includes the non-exclusive, worldwide, free-of-charge
1280             patent license to make, have made, use, offer to sell, sell, import and
1281             otherwise transfer the Package with respect to any patent claims
1282             licensable by the Copyright Holder that are necessarily infringed by the
1283             Package. If you institute patent litigation (including a cross-claim or
1284             counterclaim) against any party alleging that the Package constitutes
1285             direct or contributory patent infringement, then this Artistic License
1286             to you shall terminate on the date that such litigation is filed.
1287              
1288             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
1289             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
1290             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
1291             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
1292             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
1293             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
1294             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
1295             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1296              
1297              
1298             =cut
1299              
1300             1; # End of IMDB::Local::DB::Base