# $Id: DBI.pm 9303 2007-03-23 08:56:44Z pgollucci@p6m7g8.com $ package Apache::DBI; use strict; use constant MP2 => (exists $ENV{MOD_PERL_API_VERSION} && $ENV{MOD_PERL_API_VERSION} == 2) ? 1 : 0; BEGIN { if (MP2) { require mod_perl2; require Apache2::Module; require Apache2::ServerUtil; } elsif (defined $modperl::VERSION && $modperl::VERSION > 1 && $modperl::VERSION < 1.99) { require Apache; } } use DBI (); use Carp (); require_version DBI 1.00; $Apache::DBI::VERSION = '1.06'; # 1: report about new connect # 2: full debug output $Apache::DBI::DEBUG = 0; #DBI->trace(2); my %Connected; # cache for database handles my @ChildConnect; # connections to be established when a new # httpd child is created my %Rollback; # keeps track of pushed PerlCleanupHandler # which can do a rollback after the request # has finished my %PingTimeOut; # stores the timeout values per data_source, # a negative value de-activates ping, # default = 0 my %LastPingTime; # keeps track of last ping per data_source # Check to see if we need to reset TaintIn and TaintOut my $TaintInOut = ($DBI::VERSION >= 1.31) ? 1 : 0; sub debug { print STDERR "$_[1]\n" if $Apache::DBI::DEBUG >= $_[0]; } # supposed to be called in a startup script. # stores the data_source of all connections, which are supposed to be created # upon server startup, and creates a PerlChildInitHandler, which initiates # the connections. Provide a handler which creates all connections during # server startup sub connect_on_init { if (MP2) { if (!@ChildConnect) { my $s = Apache2::ServerUtil->server; $s->push_handlers(PerlChildInitHandler => \&childinit); } } else { Carp::carp("Apache.pm was not loaded\n") and return unless $INC{'Apache.pm'}; if (!@ChildConnect and Apache->can('push_handlers')) { Apache->push_handlers(PerlChildInitHandler => \&childinit); } } # store connections push @ChildConnect, [@_]; } # supposed to be called in a startup script. # stores the timeout per data_source for the ping function. # use a DSN without attribute settings specified within ! sub setPingTimeOut { my $class = shift; my $data_source = shift; my $timeout = shift; # sanity check if ($data_source =~ /dbi:\w+:.*/ and $timeout =~ /\-*\d+/) { $PingTimeOut{$data_source} = $timeout; } } # the connect method called from DBI::connect sub connect { my $class = shift; unshift @_, $class if ref $class; my $drh = shift; my @args = map { defined $_ ? $_ : "" } @_; my $dsn = "dbi:$drh->{Name}:$args[0]"; my $prefix = "$$ Apache::DBI "; # key of %Connected and %Rollback. my $Idx = join $;, $args[0], $args[1], $args[2]; # the hash-reference differs between calls even in the same # process, so de-reference the hash-reference if (3 == $#args and ref $args[3] eq "HASH") { # should we default to '__undef__' or something for undef values? map { $Idx .= "$;$_=" . (defined $args[3]->{$_} ? $args[3]->{$_} : '') } sort keys %{$args[3]}; } elsif (3 == $#args) { pop @args; } # don't cache connections created during server initialization; they # won't be useful after ChildInit, since multiple processes trying to # work over the same database connection simultaneously will receive # unpredictable query results. # See: http://perl.apache.org/docs/2.0/user/porting/compat.html#C__Apache__Server__Starting__and_C__Apache__Server__ReStarting_ if (MP2) { require Apache2::ServerUtil; if (Apache2::ServerUtil::restart_count() == 1) { debug(2, "$prefix skipping connection during server startup, read the docu !!"); return $drh->connect(@args); } } else { if ($Apache::ServerStarting and $Apache::ServerStarting == 1) { debug(2, "$prefix skipping connection during server startup, read the docu !!"); return $drh->connect(@args); } } # this PerlCleanupHandler is supposed to initiate a rollback after the # script has finished if AutoCommit is off. however, cleanup can only # be determined at end of handle life as begin_work may have been called # to temporarily turn off AutoCommit. if (!$Rollback{$Idx} and Apache->can('push_handlers')) { debug(2, "$prefix push PerlCleanupHandler"); if (MP2) { my $s = Apache2::ServerUtil->server; $s->push_handlers("PerlCleanupHandler", sub { cleanup($Idx) }); } else { Apache->push_handlers("PerlCleanupHandler", sub { cleanup($Idx) }); } # make sure, that the rollback is called only once for every # request, even if the script calls connect more than once $Rollback{$Idx} = 1; } # do we need to ping the database ? $PingTimeOut{$dsn} = 0 unless $PingTimeOut{$dsn}; $LastPingTime{$dsn} = 0 unless $LastPingTime{$dsn}; my $now = time; # Must ping if TimeOut = 0 else base on time my $needping = ($PingTimeOut{$dsn} == 0 or ($PingTimeOut{$dsn} > 0 and $now - $LastPingTime{$dsn} > $PingTimeOut{$dsn}) ) ? 1 : 0; debug(2, "$prefix need ping: " . ($needping == 1 ? "yes" : "no")); $LastPingTime{$dsn} = $now; # check first if there is already a database-handle cached # if this is the case, possibly verify the database-handle # using the ping-method. Use eval for checking the connection # handle in order to avoid problems (dying inside ping) when # RaiseError being on and the handle is invalid. if ($Connected{$Idx} and (!$needping or eval{$Connected{$Idx}->ping})) { debug(2, "$prefix already connected to '$Idx'"); # Force clean up of handle in case previous transaction failed to # clean up the handle &reset_startup_state($Idx); return (bless $Connected{$Idx}, 'Apache::DBI::db'); } # either there is no database handle-cached or it is not valid, # so get a new database-handle and store it in the cache delete $Connected{$Idx}; $Connected{$Idx} = $drh->connect(@args); return undef if !$Connected{$Idx}; # store the parameters of the initial connection in the handle set_startup_state($Idx); # return the new database handle debug(1, "$prefix new connect to '$Idx'"); return (bless $Connected{$Idx}, 'Apache::DBI::db'); } # The PerlChildInitHandler creates all connections during server startup. # Note: this handler runs in every child server, but not in the main server. sub childinit { my $prefix = "$$ Apache::DBI "; debug(2, "$prefix PerlChildInitHandler"); %Connected = () if MP2; if (@ChildConnect) { for my $aref (@ChildConnect) { shift @$aref; DBI->connect(@$aref); $LastPingTime{@$aref[0]} = time; } } 1; } # The PerlCleanupHandler is supposed to initiate a rollback after the script # has finished if AutoCommit is off. # Note: the PerlCleanupHandler runs after the response has been sent to # the client sub cleanup { my $Idx = shift; my $prefix = "$$ Apache::DBI "; debug(2, "$prefix PerlCleanupHandler"); my $dbh = $Connected{$Idx}; if ($Rollback{$Idx} and $dbh and $dbh->{Active} and !$dbh->{AutoCommit} and eval {$dbh->rollback}) { debug (2, "$prefix PerlCleanupHandler rollback for '$Idx'"); } delete $Rollback{$Idx}; 1; } # Store the default start state of each dbh in the handle # Note: This uses private_Apache_DBI hash ref to store it in the handle itself my @attrs = qw( AutoCommit Warn CompatMode InactiveDestroy PrintError RaiseError HandleError ShowErrorStatement TraceLevel FetchHashKeyName ChopBlanks LongReadLen LongTruncOk Taint Profile ); sub set_startup_state { my $Idx = shift; foreach my $key (@attrs) { $Connected{$Idx}->{private_Apache_DBI}{$key} = $Connected{$Idx}->{$key}; } if ($TaintInOut) { foreach my $key qw{ TaintIn TaintOut } { $Connected{$Idx}->{private_Apache_DBI}{$key} = $Connected{$Idx}->{$key}; } } 1; } # Restore the default start state of each dbh sub reset_startup_state { my $Idx = shift; # Rollback current transaction if currently in one $Connected{$Idx}->{Active} and !$Connected{$Idx}->{AutoCommit} and eval {$Connected{$Idx}->rollback}; foreach my $key (@attrs) { $Connected{$Idx}->{$key} = $Connected{$Idx}->{private_Apache_DBI}{$key}; } if ($TaintInOut) { foreach my $key qw{ TaintIn TaintOut } { $Connected{$Idx}->{$key} = $Connected{$Idx}->{private_Apache_DBI}{$key}; } } 1; } # This function can be called from other handlers to perform tasks on all # cached database handles. sub all_handlers { return \%Connected } # patch from Tim Bunce: Apache::DBI will not return a DBD ref cursor @Apache::DBI::st::ISA = ('DBI::st'); # overload disconnect { package Apache::DBI::db; no strict; @ISA=qw(DBI::db); use strict; sub disconnect { my $prefix = "$$ Apache::DBI "; Apache::DBI::debug(2, "$prefix disconnect (overloaded)"); 1; } ; } # prepare menu item for Apache::Status sub status_function { my($r, $q) = @_; my(@s) = qw(
Datasource | Username |
', join(' | ', (split($;, $_))[0,1]), " |