package JDBIx; #NOTE: do commits due to multiple records, do1 does NOT! use FileHandle; use DBI; require Exporter; @ISA = qw(Exporter); @EXPORT = qw(jdbix_autocommit jdbix_setlog jdbix_package jdbix_err jdbix_errstr); my $calling_package = 'main'; my $autocommit = 0; my $lastdb; my $logfid = 0; my $logfh = 0; sub new { my $class = shift; my $connectstr = shift; my $attrs; my $self = {}; if ($connectstr =~ /^\s*\-/) { $attrs = {$connectstr, @_}; foreach my $atb (qw(type name user pswd)) { $self->{"db$atb"} = delete $attrs->{"-$atb"} || ''; } $self->{proxy} = delete $attrs->{"-proxy"} || ''; $connectstr = "$self->{dbname},$self->{dbuser},$self->{dbpswd}"; } else { $attrs = shift; } my ($dbi_proxy, $j); foreach my $i (keys (%$attrs)) { if ($i =~ /^jdbix_/) { $j = $i; $j =~ s/^jdbix_//; $self->{$j} = $attrs->{$i}; $ENV{"\U$j\E"} = $attrs->{$i} if ($j =~ s/^env_//); delete $attrs->{$i}; } } if ($self->{proxy}) { $dbi_proxy = $self->{proxy}; $dbi_proxy =~ s/:/;port=/; $dbi_proxy = 'Proxy:hostname='.$dbi_proxy.';dsn=DBI:'; } $self->{dbtype} = $1 if ($connectstr =~ s/^(\w+)\://); $self->{dbtype} = 'ODBC' if ($self->{dbtype} =~ /odbc/i); if ($self->{dbtype} =~ /Sybase/i) { #SET IF DB DOES NOT ALLOW "?" PLACEHOLDERS IN SQL-COMMANDS! $self->{noplaceholders} = 1 unless(defined $self->{noplaceholders}); } if ($connectstr =~ s/^\s*(?:connect\=|\=)//i) { $self->{dbtype} = $1 if ($connectstr =~ /dbi\:${dbi_proxy}(\w+):/); $dbi_err = \$DBI::err; $dbi_errstr = \$DBI::errstr; ($self->{dbuser}, $self->{dbpswd}) = ($1, $2) if ($connectstr =~ s/\,(.+?)\,(.+)$//); unless ($connectstr =~ /dbi\:proxy\:/i || $dbi_proxy) { $ENV{TWO_TASK} ||= $1 if ($connectstr =~ s/\:Oracle\:(\w+)/\:Oracle\:/); $ENV{ORACLE_HOME} ||= '/home1/oracle/app/oracle/product/7.3.2' if ($connectstr =~ /\:Oracle\:/); } if ($self->{oracle8}) { $ENV{ORACLE_HOME} ||= '/app/oracle/product/8.0.5'; $ENV{ORA_NLS} ||= "$ENV{ORACLE_HOME}/ocommon/nls/admin/data"; $ENV{ORA_NLS32} ||= "$ENV{ORACLE_HOME}/ocommon/nls/admin/data"; } else { $ENV{ORACLE_HOME} ||= '/home1/oracle/app/oracle/product/7.3.2' if ($connectstr =~ /\:Oracle\:/); } my $dB = DBI->connect($connectstr,$self->{dbuser},$self->{dbpswd},$attrs); return undef unless ($dB); return undef if ($DBI::err); $self->{DBI} = $dB; $dB->{AutoCommit} = $attrs->{-AutoCommit} || $autocommit; $dB->{AutoCommit} = 1 if ($self->{dbtype} =~ /mysql/i || ($connectstr =~ /dbi\:proxy\:/i && $DBI::VERSION < 1.21)); $self->{connect} = $connectstr; } else { ($self->{dbname},$self->{dbuser},$self->{dbpswd}) = split(/,/,$connectstr); $dbi_err = \$DBI::err; $dbi_errstr = \$DBI::errstr; my $dbid; if ($self->{dbtype} =~ /oracle/i) { @dbname = split(/:/,$self->{dbname}); $self->{dbname} = 'T:' . $self->{dbname} if ($#dbname == 1); $ENV{TWO_TASK} = $self->{dbname} unless ($dbi_proxy); #if ($self->{dbname} =~ /insite/ || $self->{oracle8}) #WEBFARM'S SPECIFIC ORACLE-8 STUFF! if ($self->{oracle8}) #WEBFARM'S SPECIFIC ORACLE-8 STUFF! { $ENV{ORACLE_HOME} ||= '/app/oracle/product/8.0.5'; $ENV{ORA_NLS} ||= "$ENV{ORACLE_HOME}/ocommon/nls/admin/data"; $ENV{ORA_NLS32} ||= "$ENV{ORACLE_HOME}/ocommon/nls/admin/data"; } else { $ENV{ORACLE_HOME} ||= '/home1/oracle/app/oracle/product/7.3.2'; } $dbid = "dbi:${dbi_proxy}Oracle:"; $dbid .= $self->{dbname} if ($dbi_proxy); } else { $dbid = "dbi:${dbi_proxy}$self->{dbtype}:$self->{dbname}" } my $dB = DBI->connect($dbid,$self->{dbuser},$self->{dbpswd},$attrs); return undef unless ($dB); return undef if ($DBI::err); $self->{DBI} = $dB; if ($self->{dbtype} =~ /mysql/i || ($dbi_proxy && $DBI::VERSION < 1.21)) { $dB->{AutoCommit} = 1; } else { $dB->{AutoCommit} = $attrs->{-AutoCommit} || $autocommit; } $self->{connect} = $dbid; } $self->{calling_package} = $calling_package; $self->{dbi_proxy} = ''; $self->{autocommit} = $dB->{AutoCommit}; $self->{lasterr} = undef; $self->{err} = \$DBI::err; $self->{errstr} = \$DBI::errstr; $self->{logfid} = 0; bless $self, $class; $lastdb = $self; return $self; } sub setlog { my $self = shift; my ($mylogfid) = shift; $self->{addsemicolon} = shift || 0; # 0=no change to logfile; 1=append ';'; 2=convert ampresands for SQL-PLUS and append ';' $self->{logfh}->close if ($self->{logfid}); $self->{logfid} = 0; if ($mylogfid) { $self->{logfh} ||= new FileHandle; if ($self->{logfh} && $self->{logfh}->open(">>$mylogfid")) { $self->{logfid} = $mylogfid; return (1); } else { return (0); } } return (1); } sub jdbix_setlog { my ($mylogfid) = shift; $addsemicolon = shift || 0; # 0=no change to logfile; 1=append ';'; 2=convert ampresands for SQL-PLUS and append ';' $logfh->close if ($logfid); $logfid = 0; if ($mylogfid) { $logfh ||= new FileHandle; if ($logfh && $logfh->open(">>$mylogfid")) { $logfid = $mylogfid; return (1); } else { return (0); } } return (1); } sub setproxy { #$dbi_proxy = 'Proxy:hostname=$rhost;dsn=DBI:'; my $self = shift; my $host = shift; $lastdb = $self; if ($host) { $host =~ s/:/;port=/; $self->{proxy} = 'Proxy:hostname='.$host.';dsn=DBI:'; $self->{autocommit} = 1; } else { $self->{proxy} = ''; $self->{autocommit} = 0; } } sub package { my $self = shift; my $pkg = shift; return $self->{calling_package} unless (defined($pkg) && $pkg); $self->{calling_package} = $pkg; return 1; } sub jdbix_package { my $pkg = shift; return $calling_package unless (defined($pkg) && $pkg); $calling_package = $pkg; return 1; } sub disconnect { my $dB = shift; return undef unless ($dB->{DBI}); my $res = $dB->{DBI}->disconnect; if ($self->{logfid}) { $self->{logfh}->close; $self->{logfid} = 0; undef $self->{logfh}; } $dB->{DBI} = undef; undef $dB; return $res; } sub select { my ($self, $sqlstr, @bindvals) = @_; #bindvals ADDED 20000217! local ($_); my ($i, @res, $mycsr, $myexe, @reflistv, @refsclrv); my ($fetchcnt) = 0; my (@myline) = (); my ($cleansql) = $sqlstr; $lastdb = $self; #$nores = 1 if ($cleansql =~ s/\binto\b//i); $cleansql =~ s/\binto\b//i; #$nores = 1 unless (wantarray); #20000425: CHANGED 2 NEXT LINE (FIX BUG)! $nores = (wantarray ? 0 : 1); $cleansql =~ s/\:[^\s\,\:]+\,?//g; if ($self->{noplaceholders}) { $cleansql =~ s/([\'\"])([^\1]*?)\1/ my ($quote) = $1; my ($str) = $2; $str =~ s|\?|\x02\^2jSpR1tE\x02|g; #PROTECT ?'S IN QUOTES. "$quote$str$quote" /egs; my $t; while (@bindvals) { $t = shift(@bindvals); $t =~ s/\'/\'\'/gs; $t =~ s/\?/\x02\^2jSpR1tE\x02/gs; $cleansql =~ s/\?/\'$t\'/s; } $cleansql =~ s/\x02\^2jSpR1tE\x02/\?/gs; } if ($mycsr = $self->{DBI}->prepare($cleansql)) { if ($myexe = $mycsr->execute(@bindvals)) { if ($sqlstr =~ /\binto\b/i) { my (@varlistv) = ($sqlstr =~ /:\D\w*/g); foreach $i (@varlistv) { $i =~s/\:/$self->{calling_package}\:\:/; $i =~s/\,//; @{$i} = (); #INITIALIZE TO CLEAR ANY OLD VALUES! ${$i} = ''; push (@reflistv,\@{$i}); push (@refsclrv,\${$i}); } while ((@myline) = $mycsr->fetchrow_array()) { last if (defined($self->{DBI}->err) && $self->{DBI}->err > 0); for ($i=0;$i<=$#varlistv;$i++) { ${$refsclrv[$i]} = $myline[$i] unless ($fetchcnt); push(@{$reflistv[$i]},$myline[$i]); } push (@res,[@myline]) unless ($nores); ++$fetchcnt; }; } else { while (@myline = $mycsr->fetchrow_array()) { last if ($self->{DBI}->err > 0); push (@res,[@myline]) unless ($nores); ++$fetchcnt; }; } } else { return (); } $mycsr->finish; #$mycsr = undef; #return ( wantarray ? ('a','b','c') : $#res ); return (@res) unless ($nores); return $fetchcnt; } return (); } sub do { my ($self, $sqlstr, $ophref) = @_; #### WARNING: DO *NOT* USE SINGLE-QUOTES AROUND ANY /:PARAMETERS !!!!!!!!! #### (IT DON'T WORK!) my ($interpolate) = $ophref->{-interpolate} || $ophref; local ($_); my ($i, $j, $mycsr, $myexe, $rowcnt, @myvals, @varlistv, $logsql, $isaselect, $myline); my ($maxlistsize) = -1; my ($parmcnt) = 0; $lastdb = $self; $rowcnt = 0; unless ($interpolate) #20001117: 3RD OPTION (=3): RETURN 0 OR MORE RECORDS FOR EACH KEY { #20001117: (=2) ALWAYS RETURNS 1 OR MORE RECORDS! $rowcnt = $self->{DBI}->do($sqlstr); if ($self->{logfid} || $logfid) { $sqlstr =~ s/\s+$//; $sqlstr =~ s/^\s+//; $sqlstr .= ';' if ($self->{addsemicolon}); $sqlstr =~ s/\&/\'\|\|\'\&\'\|\|\'/g if ($self->{addsemicolon} == 1); if ($self->{logfid}) { my $logfh = $self->{logfh}; #NEEDED FOR PERL TO COMPILE. flock $logfh, 2; #EXCLUSIVE LOCK. print $logfh "$sqlstr\n"; flock $logfh, 8; #UNLOCK. } if ($logfid) { flock $logfh, 2; #EXCLUSIVE LOCK. print $logfh "$sqlstr\n"; flock $logfh, 8; #UNLOCK. } } $self->commit() if ($ophref->{-commit}); return (undef) if ($self->{DBI}->err < 0); return ($rowcnt); } my $varliststart = 0; #NEXT 15 ADDED 20011207 TO FIX INCORRECT MAXLISTSIZE ON SELECTS. if ($sqlstr =~ /^\s*select\s/i) #("INTO" VBLES NEEDED BLANKING *BEFORE* MAXLISTSIZE CALCULATED!) { $isaselect = 1; while ($sqlstr =~ s#(into[\s\?\,]+)(\/\:[a-zA-Z_][^,\)\s\"]*)#$1\?#i) { push (@varlistv,$2); ++$parmcnt; } for ($i=0;$i<=$#varlistv;$i++) { $varlistv[$i] =~ s#/\:#$self->{calling_package}\:\:#; } $varliststart = scalar(@varlistv); } while ($sqlstr =~ s#\'?(\/\:[a-zA-Z_][^,\)\s\"]*)#\?#) { push (@varlistv,$1); ++$parmcnt; } for ($i=$varliststart;$i<=$#varlistv;$i++) { $varlistv[$i] =~ s#/\:#$self->{calling_package}\:\:#; if ($#{$varlistv[$i]} >= 0) { $maxlistsize = $#{$varlistv[$i]} if ($#{$varlistv[$i]} > $maxlistsize); } } $sqlstr =~ s/'(\:\d+)'/$1/g; #$sqlstr =~ s/'\?'/\?/g; #REMOVED 20010125 TO FIX ERROR WHEN USER ENTRY WAS JUST A SINGLE QUESTION-MARK! my ($cleansql) = $sqlstr; $selectargcnt = 0; if ($isaselect) { $cleansql =~ s/\binto\b([\s\?\,]*)//i; my ($t) = $1; ++$selectargcnt while ($t =~ /\?/g); } else { ###########$sqlstr =~ s/\n/ /gs; if ($self->{dbtype} =~ /odbc/i && $sqlstr =~ /insert.*\w+\.NEXTVAL/s) { $sqlstr = &fixNEXTVAL($self, $sqlstr); } elsif ($self->{dbtype} =~ /mysql/i) { $sqlstr =~ s/([\,\(]\s*)\w+\.NEXTVAL(\s*[\,\)])/$1NULL$2/g; #MYSQL DOES SEQUENCES DIFFERENTLY (see AUTO_INCREMENT)! } #ADDED 20030904 TO MAKE SEQUENCE-RETRIEVAL DATABASE-INDEPENDENT. if ($sqlstr =~ /insert\s+into\s+(\w+).*?(\w+)\.NEXTVAL/si) { $self->{lastsequencetable} = $1; $self->{lastsequencename} = $2; } } for ($i=0;$i<$selectargcnt;$i++) { @{$varlistv[$i]} = (); #INITIALIZE TO CLEAR ANY OLD VALUES ${$varlistv[$i]} = ''; #FROM VBLES TO RECEIVE SELECT INPUTS. } my $cleantemplate; if ($self->{noplaceholders}) { $cleansql =~ s/([\'\"])([^\1]*?)\1/ my ($quote) = $1; my ($str) = $2; $str =~ s|\?|\x02\^2jSpR1tE\x02|g; #PROTECT ?'S IN QUOTES. "$quote$str$quote" /egs; $cleansql =~ s/\?/\x02\^3jSpR1tE\x02/gs; #PROTECT BINDING ?S. $cleansql =~ s/\x02\^2jSpR1tE\x02/\?/gs; #UNPROTECT ?'S IN QUOTES. $cleantemplate = $cleansql; } else { $mycsr = $self->{DBI}->prepare($cleansql) if ($isaselect); } $maxlistsize = 0 if ($maxlistsize < 0); my ($ii) = 0; my $bindok; #------------------------------------------------------------- local *select_noplaceholders = sub { my $t; for ($i=0;$i<=$maxlistsize;$i++) { $cleansql = $cleantemplate; for ($j=$selectargcnt;$j<=$parmcnt-1;$j++) { $t = defined($#{$varlistv[$j]}) ? ${$varlistv[$j]}[$i] : ${$varlistv[$j]}; $t =~ s/\'/\'\'/gs; $t =~ s/\?/\x02\^3jSpR1tE\x02/gs; $cleansql =~ s/\x02\^3jSpR1tE\x02/\'$t\'/s; } @myline = (); $mycsr = $self->{DBI}->prepare($cleansql); return 0 if ($self->{DBI}->err); #ADDED 20010517 TO CATCH ERRORS! $res = $mycsr->execute(); return 0 if ($self->{DBI}->err); #ADDED 20010517 TO CATCH ERRORS! #return 0 unless ($res > 0 || $mycsr); @myline = $mycsr->fetchrow_array(); if ($#myline >= 0 || $interpolate < 3) #IF ADDED 20001117 { for ($j=0;$j<=$selectargcnt-1;$j++) { $myline[$j] = '' if ($res eq 'OK'); #ADDED 19991011 JWT. if (defined($#{$varlistv[$j]})) { ${$varlistv[$j]}[$ii] = $myline[$j]; } else { ${$varlistv[$j]} = $myline[$j]; } } ++$ii; ++$rowcnt; } if ($interpolate >= 2) #20001117 { II: while (@myline = $mycsr->fetchrow_array()) { for ($j=0;$j<=$selectargcnt-1;$j++) { if (defined($#{$varlistv[$j]})) { ${$varlistv[$j]}[$ii] = $myline[$j]; } else { ${$varlistv[$j]} = $myline[$j]; } } ++$ii; ++$rowcnt; } } $mycsr->finish; if ($ophref->{-commit}) { $self->commit() unless ($rowcnt % 20); } } return 1; }; local *select_placeholders = sub { for ($i=0;$i<=$maxlistsize;$i++) { @myvals = (); for ($j=$selectargcnt;$j<=$parmcnt-1;$j++) { if (defined($#{$varlistv[$j]})) { push(@myvals,${$varlistv[$j]}[$i]); ###$mycsr->bind_param($j1, ${$varlistv[$j]}[$i]) if ($mycsr); } else { push(@myvals,${$varlistv[$j]}); } } $res = $mycsr->execute(@myvals); return 0 if ($self->{DBI}->err); #ADDED 20010517 TO CATCH ERRORS! @myline = $mycsr->fetchrow_array(); if ($#myline >= 0 || $interpolate < 3) #IF ADDED 20001117 { for ($j=0;$j<=$selectargcnt-1;$j++) { $myline[$j] = '' if ($res eq 'OK'); #ADDED 19991011 JWT. if (defined($#{$varlistv[$j]})) { ${$varlistv[$j]}[$ii] = $myline[$j]; } else { ${$varlistv[$j]} = $myline[$j]; } } ++$ii; ++$rowcnt; } if ($interpolate >= 2) #20001117 { II: if (scalar(@myline)) #TEST ADDED 20040211 TO PREVENT RAISE-ERROR ERROR. { while (@myline = $mycsr->fetchrow_array()) { for ($j=0;$j<=$selectargcnt-1;$j++) { if (defined($#{$varlistv[$j]})) { ${$varlistv[$j]}[$ii] = $myline[$j]; } else { ${$varlistv[$j]} = $myline[$j]; } } ++$ii; ++$rowcnt; } } } $mycsr->finish; if ($ophref->{-commit}) { $self->commit() unless ($rowcnt % 20); } } return 1; }; local *nonselect_noplaceholders = sub { my $t; for ($i=0;$i<=$maxlistsize;$i++) { @myvals = (); $cleansql = $cleantemplate; for ($j=$selectargcnt;$j<=$parmcnt-1;$j++) { $t = defined($#{$varlistv[$j]}) ? ${$varlistv[$j]}[$i] : ${$varlistv[$j]}; $t =~ s/\'/\'\'/gs; $t =~ s/\?/\x02\^3jSpR1tE\x02/gs; $cleansql =~ s/\x02\^3jSpR1tE\x02/\'$t\'/s; } @myline = (); $res = $self->{DBI}->do($cleansql); return 0 unless ($res eq '0E0' || $res > 0); ++$rowcnt; if ($self->{logfid} || $logfid) { $logsql = $cleansql; $logsql =~ s/\s+$//; $logsql =~ s/^\s+//; $logsql .= ';' if ($self->{addsemicolon}); $sqlstr =~ s/\&/\'\|\|\'\&\'\|\|\'/g if ($self->{addsemicolon} == 1); if ($self->{logfid}) { my $logfh = $self->{logfh}; #NEEDED FOR PERL TO COMPILE. flock $logfh, 2; #EXCLUSIVE LOCK. print $logfh "$logsql\n"; flock $logfh, 8; #UNLOCK. } if ($logfid) { flock $logfh, 2; #EXCLUSIVE LOCK. print $logfh "$logsql\n"; flock $logfh, 8; #UNLOCK. } } if ($ophref->{-commit}) { $self->commit() unless ($rowcnt % 20); } } return 1; }; local *nonselect_placeholders = sub { for ($i=0;$i<=$maxlistsize;$i++) { @myvals = (); for ($j=$selectargcnt;$j<=$parmcnt-1;$j++) { if (defined($#{$varlistv[$j]})) { push(@myvals,${$varlistv[$j]}[$i]); ###$mycsr->bind_param($j1, ${$varlistv[$j]}[$i]) if ($mycsr); } else { push(@myvals,${$varlistv[$j]}); } } @myline = (); $res = $self->{DBI}->do($cleansql,{},@myvals); return 0 unless ($res eq '0E0' || $res > 0); ++$rowcnt; if ($self->{logfid} || $logfid) { #my ($j1); $logsql = $cleansql; #for $j (0..$#myvals) for ($j=0;$j<=$#myvals;$j++) { #$j1 = $j + 1; #$logsql =~ s/\:$j1/\'$myvals[$j]\'/g; $logsql =~ s/\?/\'$myvals[$j]\'/; } $logsql =~ s/\s+$//; $logsql =~ s/^\s+//; $logsql .= ';' if ($self->{addsemicolon}); $sqlstr =~ s/\&/\'\|\|\'\&\'\|\|\'/g if ($self->{addsemicolon} == 1); if ($self->{logfid}) { my $logfh = $self->{logfh}; #NEEDED FOR PERL TO COMPILE. flock $logfh, 2; #EXCLUSIVE LOCK. print $logfh "$logsql\n"; flock $logfh, 8; #UNLOCK. } if ($logfid) { flock $logfh, 2; #EXCLUSIVE LOCK. print $logfh "$logsql\n"; flock $logfh, 8; #UNLOCK. } } if ($ophref->{-commit}) { $self->commit() unless ($rowcnt % 20); } } return 1; }; #------------------------------------------------------------- if ($isaselect) { if ($self->{noplaceholders}) { $bindok = &select_noplaceholders(); } else { $bindok = &select_placeholders(); } } else { if ($self->{noplaceholders}) { $bindok = &nonselect_noplaceholders(); } else { $bindok = &nonselect_placeholders(); } } if ($bindok) { $self->commit() if ($ophref->{-commit}); #NEXT IF ADDED 20040120 TO SORT VECTORIZED SELECTS (WHICH DON'T SORT #OTHERWISE) WHEN "ORDER-BY" CLAUSE PRESENT. NOTE: ONLY SELECT VECTORS #ARE SORTED (NOT KEY VECTORS) UNLESS INTERPOLATE = 1 (INTERPOLATE = 1 #IMPLIES A PROMISE THAT *ALL* VECTORS WILL BE *SAME* LENGTH!!!!!!!! if ($isaselect && $selectargcnt > 0 && $rowcnt > 1) { if ($sqlstr =~ /\s*order\s+by\s+([\w\.\, ]+)$/is) { my $ordbyclause = $1; my $sortavailable; eval {require 'sort_elements.pl' and $sortavailable = 1;}; if ($sortavailable) { my @sortfields = split(/\,/, $ordbyclause); my @sortorders = (); for (my $i=0;$i<=$#sortfields;$i++) #ASCENDING VS. DESCENDING. { $sortorders[$i] = ($sortfields[$i] =~ s/\s+desc//is) ? '-' : ''; $sortfields[$i] =~ s/\s//g; } if ($sqlstr =~ /select\s+([a-z\.\,_\s]+)into/is) { my $selfields = $1; my @selfields = split(/\,/, $selfields); my @sortvec; for (my $i=0;$i<=$#selfields;$i++) { $selfields[$i] =~ s/\s//g; } #DETERMINE WHICH ARRAYS TO SORT BY (SORTVEC). for (my $j=0;$j<=$#sortfields;$j++) { for (my $i=0;$i<=$#selfields;$i++) { if ($selfields[$i] eq $sortfields[$j]) { push (@sortvec, "$sortorders[$j]$i"); } } } my $sortcmd; if ($interpolate == 1) #SORT *ALL* VECTORS INCL. KEYS! { $sortcmd = "&sort_elements_by_list([" .join(',',@sortvec).'], [], \@' .join(',\@', @varlistv).');'; } else #ONLY SORT NON-KEY VECTORS (KEY VECTORS NOT SAME LENGTH). { #WARNING - KEY VECTORS WILL NOT LINE UP WITH SELECT VECTORS NOW! $sortcmd = "&sort_elements_by_list([" .join(',',@sortvec).'], []'; for (my $i=0;$i<=$#selfields;$i++) { $sortcmd .= ',\@'.$varlistv[$i]; } $sortcmd .= ');'; } eval $sortcmd; } } } } return $rowcnt; } else { NOBIND: return undef; } } sub commit { my ($self) = shift; $lastdb = $self; #return $self->{DBI}->commit; #AUTOCOMMIT IS ON! return $self->{DBI}->commit unless ($self->{DBI}->{AutoCommit}); #AUTOCOMMIT IS ON! return 1; } sub rollback { my ($self) = shift; return $self->{DBI}->rollback unless ($self->{DBI}->{AutoCommit}); #AUTOCOMMIT IS ON! return 1; } sub autocommit #CALL WITH 1 ARG UNLESS DB ALREADY OPEN! { my ($self) = shift; my ($ac) = shift; return $self->{autocommit} unless (defined($ac) && $ac); $lastdb = $self; $self->{DBI}->{AutoCommit} = $ac; $self->{autocommit} = $ac; return 1; } sub jdbix_autocommit #CALL WITH 1 ARG UNLESS DB ALREADY OPEN! { my ($ac) = shift; return $autocommit unless (defined($ac) && $ac); $autocommit = $ac; return 1; } sub jdbix_err { my ($self) = shift || $lastdb; return $self ? $self->err() : $DBI::err; } sub err { my $self = shift; return $self->{DBI}->err if ($self->{DBI} && $self->{DBI}->err); return ${$self->{err}} if (ref $self->{err}); return $DBI::err if ($DBI::err); return undef; } sub jdbix_errstr { my ($self) = shift || $lastdb; # return 'Not logged in, invalid database, id, password?' unless ($self); return $self ? $self->errstr() : $DBI::errstr; } sub errstr { my $self = shift; return $self->{DBI}->errstr if ($self->{DBI} && $self->{DBI}->errstr); return $$self->{errstr} if (ref $self->{errstr}); return $DBI::errstr if ($DBI::errstr); return $self->err(); } package JDBIx::csr; use DBI; sub new { my $class = shift; my $self = {}; bless $self, $class; return $self; } sub bind #NOTE: BIND ARGS MUST APPEAR IN ORDER (ie. :1, :2, :3)! { my ($mycsr, @bindvals) = @_; my ($t); $lastdb = $mycsr->{dB}; my $fetchcnt = 0; my $cleansql = $mycsr->{cleansql}; my $sqlstr = $mycsr->{sql}; if ($mycsr->{dB}->{noplaceholders}) { $mycsr->{csr}->finish() if ($mycsr->{csr}); while (@bindvals) { $t = shift(@bindvals); $t =~ s/\'/\'\'/gs; $t =~ s/\?/\x02\^2jSpR1tE\x02/gs; $sqlstr =~ s/\x02\^3jSpR1tE\x02/\'$t\'/s; } $mycsr->{csr} = $mycsr->{dB}->{DBI}->prepare($sqlstr); return undef unless ($mycsr->{csr}); } my ($res) = $mycsr->{csr}->execute(@bindvals); return undef if ($mycsr->{csr}->err); if ($mycsr->{select}) { for (my $i=0;$i<=$mycsr->{varcnt};$i++) { ${${$mycsr->{refsclrv}}[$i]} = ''; @{${$mycsr->{reflistv}}[$i]} = (); } if ($mycsr->{varcnt} >= 0) { my (@myline); $fetchcnt = 0; while ((@myline) = $mycsr->{csr}->fetchrow_array()) { last if ($mycsr->{dB}->{DBI}->err > 0); for (my $i=0;$i<=$mycsr->{varcnt};$i++) { ${${$mycsr->{refsclrv}}[$i]} = $myline[$i] unless ($fetchcnt); #CORRECTED ABOVE BUG 20001205 WHICHED RETURNED LAST RECORD (SHOULD BE 1ST)! push(@{${$mycsr->{reflistv}}[$i]},$myline[$i]); } ++$fetchcnt; }; } } else #ADDED 20010228 TO LOG NON-SELECTS DONE W/BIND! { #NEXT 5 ADDED 20030922 TO MAKE SEQUENCE-RETRIEVAL DATABASE-INDEPENDENT. if ($sqlstr =~ /insert\s+into\s+(\w+).*?(\w+)\.NEXTVAL/si) { $mycsr->{dB}->{lastsequencetable} = $1; $mycsr->{dB}->{lastsequencename} = $2; } if ($mycsr->{dB}->{logfid} || $logfid) { $sqlstr =~ s/\s+$//; $sqlstr =~ s/^\s+//; for (my $i=0;$i<=$#bindvals;$i++) { $t = $bindvals[$i]; $t =~ s/\'/\'\'/g; $t =~ s/\?/\x02\^2jSpR1tE\x02/gs; $sqlstr =~ s/\x02\^3jSpR1tE\x02/\'$t\'/s; } $sqlstr .= ';' if ($mycsr->{$dB}->{addsemicolon}); $sqlstr =~ s/\&/\'\|\|\'\&\'\|\|\'/g if ($mycsr->{$dB}->{addsemicolon} == 1); if ($mycsr->{dB}->{logfid}) { my $logfh = $mycsr->{dB}->{logfh}; #NEEDED FOR PERL TO COMPILE. flock $logfh, 2; #EXCLUSIVE LOCK. print $logfh "$sqlstr\n"; flock $logfh, 8; #UNLOCK. } if ($logfid) { flock $logfh, 2; #EXCLUSIVE LOCK. print $logfh "$sqlstr\n"; flock $logfh, 8; #UNLOCK. } } } if ($fetchcnt) #ADDED 20001205 TO FIX ERRONIOUS RESULT FROM ORACLE DBI::PROXY OF 250. { return $fetchcnt; } elsif ($res) { return $mycsr->{csr}->rows ? $mycsr->{csr}->rows : $res; } else { return $res; } } sub fetch { my $mycsr = shift; $lastdb = $mycsr->{dB}; my (@myline) = $mycsr->{csr}->fetchrow_array(); if ($mycsr->{select}) { for (my $i=0;$i<=$mycsr->{varcnt};$i++) { ${${$mycsr->{refsclrv}}[$i]} = $myline[$i]; } } return (@myline); } sub fetchall { my $mycsr = shift; my ($myline, @res, $nores); $lastdb = $mycsr->{dB}; $nores = 1 unless (wantarray); my ($fetchcnt) = 0; while ((@myline) = $mycsr->{csr}->fetchrow_array()) { #THIS FOR-LOOP IS CURRENTLY DEPRECIATED BECAUSE FETCH WILL NOT RETURN VALUES IF VARCNT > 0 (THEY'VE ALREADY BEEN FETCHED BY BIND)! for (my $i=0;$i<=$mycsr->{varcnt};$i++) { ${${$mycsr->{refsclrv}}[$i]} = $myline[$i] unless ($fetchcnt); push (@{${$mycsr->{reflistv}}[$i]}, $myline[$i]); } push (@res, [@myline]) unless ($nores); ++$fetchcnt; } return (@res) unless ($nores); return $fetchcnt; } sub closecsr { my $mycsr = shift; return undef unless ($mycsr->{csr}); my $res = $mycsr->{csr}->finish(); $mycsr->{csr} = null; return $res; } package JDBIx; sub opencsr { my ($self, $sqlstr, $xeqflag) = @_; $lastdb = $self; $sqlstr =~ s/\:\d+/\?/g unless ($xeqflag); my ($myres, $cleansql); my ($mycsr) = new JDBIx::csr(); $cleansql = $sqlstr; if ($sqlstr =~ /^\s*select\s/i) { $mycsr->{select} = 1; if ($sqlstr =~ /\binto\b/i) { my (@varlistv) = ($sqlstr =~ /:\D\w*/g); foreach $i (@varlistv) { $i =~s/\:/$self->{calling_package}\:\:/; $i =~s/,//; @{$i} = (); #INITIALIZE TO CLEAR ANY OLD VALUES! ${$i} = ''; push (@{$mycsr->{reflistv}},\@{$i}); push (@{$mycsr->{refsclrv}},\${$i}); } $mycsr->{varcnt} = $#varlistv; $cleansql =~ s/\binto\b//i; $nores = 1 unless (wantarray); $cleansql =~ s/\:[^\s\,\:]+\,?//g; } else { $mycsr->{varcnt} = -1; } } else { $mycsr->{select} = 0; } if ($self->{noplaceholders} && !$xeqflag) { $cleansql =~ s/([\'\"])([^\1]*?)\1/ my ($quote) = $1; my ($str) = $2; $str =~ s|\?|\x02\^2jSpR1tE\x02|g; #PROTECT ?'S IN QUOTES. "$quote$str$quote" /egs; $cleansql =~ s/\?/\x02\^3jSpR1tE\x02/gs; #PROTECT OTHER(BIND) ?'S $cleansql =~ s/\x02\^2jSpR1tE\x02/\?/gs; #UNPROTECT ?'S IN QUOTES. $mycsr->{sql} = $cleansql; #ADDED 20010228 FOR USE IN BIND. $mycsr->{cleansql} = $cleansql; #ADDED 20020715 FOR USE IN BIND. $mycsr->{cleansql} =~ s/\x02\^3jSpR1tE\x02/\?/gs; $mycsr->{dB} = $self; #ADDED 20010228 FOR USE IN BIND. } else { $mycsr->{cleansql} = $cleansql; #ADDED 20020715 FOR USE IN BIND. $mycsr->{dB} = $self; #ADDED 20010228 FOR USE IN BIND. $mycsr->{csr} = $self->{DBI}->prepare($cleansql); return undef unless (defined($mycsr->{csr})); if ($xeqflag && $mycsr->{csr}) { $myres = $mycsr->{csr}->execute(); if ($self->{logfid} || $logfid) { $cleansql =~ s/\s+$//; $cleansql =~ s/^\s+//; $cleansql .= ';' if ($self->{addsemicolon}); $cleansql =~ s/\&/\'\|\|\'\&\'\|\|\'/g if ($self->{addsemicolon} == 1); if ($self->{logfid}) { my $logfh = $self->{logfh}; #NEEDED FOR PERL TO COMPILE. flock $logfh, 2; #EXCLUSIVE LOCK. print $logfh "$cleansql\n"; flock $logfh, 8; #UNLOCK. } if ($logfid) { flock $logfh, 2; #EXCLUSIVE LOCK. print $logfh "$cleansql\n"; flock $logfh, 8; #UNLOCK. } } } $cleansql =~ s/\?/\x02\^3jSpR1tE\x02/gs; #PROTECT OTHER(BIND) ?'S $mycsr->{sql} = $cleansql; #ADDED 20010228 FOR USE IN BIND. } return wantarray ? ($mycsr, $myres) : $mycsr; } sub do1 { my ($self, $sqlstr, @bindvals) = @_; my ($res, $t); $lastdb = $self; $sqlstr =~ s/\:\d+/\?/g if ($#bindvals >= 0); if ($self->{dbtype} =~ /odbc/i && $sqlstr =~ /insert.*\w+\.NEXTVAL/s) { $sqlstr = &fixNEXTVAL($self, $sqlstr); } else { $sqlstr =~ s/([\,\(]\s*)\w+\.NEXTVAL(\s*[\,\)])/$1NULL$2/g if ($self->{dbtype} =~ /mysql/i); #MYSQL DOES SEQUENCES DIFFERENTLY (see AUTO_INCREMENT)! } #ADDED 20030904 TO MAKE SEQUENCE-RETRIEVAL DATABASE-INDEPENDENT. if ($sqlstr =~ /insert\s+into\s+(\w+).*?(\w+)\.NEXTVAL/si) { $self->{lastsequencetable} = $1; $self->{lastsequencename} = $2; } if ($self->{noplaceholders}) { $sqlstr =~ s/([\'\"])([^\1]*?)\1/ my ($quote) = $1; my ($str) = $2; $str =~ s|\?|\x02\^2jSpR1tE\x02|g; #PROTECT ?'S IN QUOTES. "$quote$str$quote" /egs; while (@bindvals) { $t = shift(@bindvals); $t =~ s/\'/\'\'/s; $t =~ s/\?/\x02\^2jSpR1tE\x02/gs; $t =~ s/\?/\x02\^2jSpR1tE\x02/gs; $sqlstr =~ s/\?/\'$t\'/s; } $sqlstr =~ s/\x02\^2jSpR1tE\x02/\?/gs; #UNPROTECT ?'S. } my ($mycsr) = $self->{DBI}->prepare($sqlstr); if ($mycsr) { #if ($sqlstr =~ /^\s+select/i) #CHGD TO NEXT 20011121! if ($sqlstr =~ /^\s*select/i) { $res = $mycsr->execute(@bindvals); #bindvals ADDED HERE 20000217. return undef unless(defined($res)); if ($res) { $res = $mycsr->rows if ($mycsr->rows); } if ($res > 0 || $res eq '0E0') { @myline = $mycsr->fetchrow_array(); $mycsr->finish; return wantarray ? ($res, @myline) : $res; } } else { $res = $mycsr->execute(@bindvals); return undef unless(defined($res)); if ($self->{logfid} || $logfid) { $sqlstr =~ s/\s+$//; $sqlstr =~ s/^\s+//; if ($#bindvals >= 0) { $sqlstr =~ s/([\'\"])([^\1]*?)\1/ my ($quote) = $1; my ($str) = $2; $str =~ s|\?|\x02\^2jSpR1tE\x02|g; #PROTECT ?'S IN QUOTES. "$quote$str$quote" /egs; for (my $i=0;$i<=$#bindvals;$i++) { $t = $bindvals[$i]; $t =~ s/\'/\'\'/g; $t =~ s/\?/\x02\^2jSpR1tE\x02/gs; $sqlstr =~ s/\?/\'$t\'/; } $sqlstr =~ s/\x02\^2jSpR1tE\x02/\?/gs; #UNPROTECT ?'S. } $sqlstr .= ';' if ($self->{addsemicolon}); $sqlstr =~ s/\&/\'\|\|\'\&\'\|\|\'/g if ($self->{addsemicolon} == 1); if ($self->{logfid}) { my $logfh = $self->{logfh}; #NEEDED FOR PERL TO COMPILE. flock $logfh, 2; #EXCLUSIVE LOCK. print $logfh "$sqlstr\n"; flock $logfh, 8; #UNLOCK. } if ($logfid) { flock $logfh, 2; #EXCLUSIVE LOCK. print $logfh "$sqlstr\n"; flock $logfh, 8; #UNLOCK. } } if ($res > 0 || $res eq '0E0') { $res = $mycsr->rows if ($mycsr->rows); $mycsr->finish; return wantarray ? ($res) : $res; } } $mycsr->finish; } return undef; } sub fetchseq #ADDED 20030904 TO MAKE SEQUENCE-RETRIEVAL DATABASE-INDEPENDENT. { my $self = shift; my $seqfield = shift; my $seqname = shift || $self->{lastsequencename}; my $tablename = shift || $self->{lastsequencetable}; #ADD CODE FOR YOUR FAVORITE DATABASE HERE, OR TAKE THE DEFAULT (LAST OPTION)! if ($self->{dbtype} eq 'Sprite') { return $self->{DBI}->{sprite_insertid} if (defined $self->{DBI}->{sprite_insertid} && $self->{DBI}->{sprite_insertid} =~ /\d/); } if ($self->{dbtype} =~ /(?:Oracle|Sprite)/) { my $mycsr; if ($mycsr = $self->{DBI}->prepare("select $seqname.CURRVAL from DUAL")) { my $myexe; if ($myexe = $mycsr->execute()) { ($lastseq) = $mycsr->fetchrow_array(); $mycsr->finish(); return $lastseq if ($lastseq =~ /\d/); } } } elsif ($self->{dbtype} =~ /mysql/) { return $self->{DBI}->{mysql_insertid} if (defined $self->{DBI}->{mysql_insertid} && $self->{DBI}->{mysql_insertid} =~ /\d/); } if ($seqfield) #IF ALL ELSE FAILS, FETCH A DESCENDING LIST OF VALUES FOR THE FIELD THE SEQUENCE WAS INSERTED INTO (USER MUST SPECIFY THE FIELD!) { my $sql = <{DBI}->prepare($sql)) { my $myexe; if ($myexe = $mycsr->execute()) { ($lastseq) = $mycsr->fetchrow_array(); $mycsr->finish(); return $lastseq; } else { } } return undef; } return undef; } sub fetchnextseq { my $self = shift; my $seqname = shift || '.dbixseq'; #ADD CODE FOR YOUR FAVORITE DATABASE HERE, OR TAKE THE DEFAULT (LAST OPTION)! if ($self->{dbtype} =~ /(?:Oracle|Sprite)/) { my $mycsr; if ($mycsr = $self->{DBI}->prepare("select $seqname.NEXTVAL from DUAL")) { my $myexe; if ($myexe = $mycsr->execute()) { ($lastseq) = $mycsr->fetchrow_array(); $mycsr->finish(); return $lastseq if ($lastseq =~ /\d/); } } } elsif ($self->{dbtype} =~ /mysql/) { return $self->{DBI}->{mysql_insertid} if (defined $self->{DBI}->{mysql_insertid} && $self->{DBI}->{mysql_insertid} =~ /\d/); } #IF ALL ELSE FAILS, USE OUR OWN SPECIAL SEQUENCE FILE! my $seq_file = $ENV{HOME}; $seq_file .= '/' unless ($seq_file =~ m#\/$#); $seq_file = '' if ($seq_file eq '/'); $seq_file .= $seqname; if (open(T, "<$seq_file")) { my $x = ; chomp($x); my ($seqval, $seqincby) = split(/,/,$x); close (T); } else { $seqval = 0; $seqincby = 1; } if (open (T, ">$seq_file")) { $seqval += ($seqincby || 1); print T "$seqval,$seqincby\n"; close (T); return $seqval; } else { $lasterr = "$@/$? (file:$seq_file)"; } return undef; } sub fixNEXTVAL { my ($self, $sqlstr) = @_; if ($sqlstr =~ /^\s*insert\s+into\s+ # Keyword (\S+)\s* # Table (?:\((.+?)\)\s*)? # Keys values\s* # 'values' \((.+)\)\s*$/isxo) { my ($table, $columns, $values) = ($1, $2, $3); my ($origvalues) = $values; $columns =~ s/\s//g; unless ($columns =~ /\S/) { my $csr = $self->{DBI}->prepare("select * from $table"); $csr->execute(); $columns = join(',', @{$csr->{NAME}}); $csr->finish(); } my (@columns) = split(/,/, $columns); $columns = ''; $values =~ s/\\\\/\x02/g; #PROTECT "\\" $values =~ s/\\\'/\x03/g; #PROTECT "", \", '', AND \'. $values =~ s/\'(.*?)\'/ my ($j)=$1; $j =~ s|,|\x04|g; #PROTECT "," IN QUOTES. "'$j'" /eg; @values = split(/,/,$values); for $i (0..$#values) { $values[$i] =~ s/^\s+//; #STRIP LEADING & TRAILING SPACES. $values[$i] =~ s/\s+$//; $values[$i] =~ s/\x03/\'\'/g; #RESTORE PROTECTED SINGLE QUOTES HERE. $values[$i] =~ s/\x02/\\/g; #RESTORE PROTECTED SINGLE QUOTES HERE. $values[$i] =~ s/\x04/,/g; #RESTORE PROTECTED SINGLE QUOTES HERE. if ($values[$i] =~ /\s*(\w+).NEXTVAL\s*$/) { my ($t) = $1; $origvalues =~ s/\s*$t\.NEXTVAL\s*\,?\s*//; } else { $columns .= $columns[$i] . ','; } }; chop ($columns); $sqlstr = "insert into $table ($columns) values ($origvalues) "; } return ($sqlstr); } 1 __END__ =head1 NAME JDBIx - Object-oriented flavor of DBIx. DBIx - Perl extension for DBI, providing a higher-level abstraction for more database-independence. =head1 AUTHOR This module is Copyright (C) 2000 by Jim Turner Email: turnerjw784 - at - yahoo .dot com All rights reserved. You may distribute this module under the terms of either the GNU General Public License or the Artistic License, as specified in the Perl README file. =head1 SYNOPSIS / DESCRIPTION. use JDBIx; I<&jdbix_setlog>('/tmp/dbixlogt.txt'); #OPTIONALLY, SET UP A LOG FILE. #B. my $dB = I<&new JDBIx>("dbname,dbuser,dbpswd",{}) || die "-no login: err=".I<&jdbix_err>().':'.I<&jdbix_errstr>."=\n"); I<$dB->package>(__PACKAGE__); #UNLESS PACKAGE IS "main::". I<&set_databasetype>('Oracle'); I<&setproxy>('remotehost.domain.com:8016'); #IF USING REMOTE DBI::Proxy. $dB->{LongTruncOk} = 1; #ALLOW SILENT TRUNCATION! #DO A SIMPLE SELECT, STORING THE RESULTS INTO @f1 AND @f2. THE 1ST RECORD #IS STORED INTO $f1 and $f2. ($res = I<$dB->select>($dB, "select field1, field2 into :f1, :f2 from test")) || warn 'Could not do select ('.I<&jdbix_err>().'.'.I<&jdbix_errstr>().')!'); for (my $i=0;$i<=$res;$i++) { print "-For record# $i: field1=$f1[$i], field2=$f2[$i]\n"; } #B. @res = I<$dB->select>("select field1, field2 from test"); for (my $i=0;$i<=$#res;$i++) { print "\n-For record# $i: "; for (my $j=0;$j<=$#{$res[$i]};$j++) { print "value =$res[$i]->[$j],\t"; } } #B. $sqlstr = <($sqlstr, 1); #B. ($res = I<$dB->do1>($dB, 'insert into test values (?, ?, ?, ?)', 'value1', $value2, $value3, 'value4')) || die ('Could not insert record ('.I<&jdbix_err>().'.'.I<&jdbix_errstr>().')!'); #B I<$dB->commit>(); #B. $sqlstr = <($sqlstr); die "Could not open ($sqlstr)!" unless ($csr); #AN OPTIONAL 3RD ARGUMENT TO I CAUSES $dbh->execute() TO BE CALLED IN ADDITION TO $dbh->prepare(). IF THE RETURN VARIABLE IS A LIST, A SECOND VALUE IS RETURNED REPRESENTING THE RESULT RETURNED BY $dbh->execute. ALSO, IF USED FOR A SELECT STATEMENT, VARIABLES CAN BE BOUND JUST AS FOR select (see ":f1" and ":f2" in prev. examples). for (my $i=0;$i<=$#f1;$i++) { I<$dB->bind>($csr, $i, $f1[$i]); } I<$dB->close>($csr); #B. $sqlstr = <($sqlstr); die "Could not open ($sqlstr)!" unless ($csr); for (my $i=0;$i<=$#f1;$i++) { I<$dB->bind>($csr, $f1[$i]); while (($f3, $f4) = I<$dB->fetch>($csr)) { print "-For record# $i (key $f1[$i]): field3=$f3, field4=$f4\n"; } } I<$dB->close>($csr); #B. I<$dB->disconnect>(); =head1 TODO Make a more object-oriented version. =head1 KNOWN BUGS -none (yet)- =head1 SEE ALSO DBI(3), perl(1) =cut