/usr/local/CPAN/OOPS/OOPS/DBO.pm
#
# base class for database connectors
#
BEGIN {
Filter::Util::Call::filter_add(\&OOPS::SelfFilter::filter)
unless $OOPS::SelfFilter::defeat;
}
package OOPS::DBO;
use strict;
use warnings;
use Carp;
use UNIVERSAL qw(can);
use Scalar::Util qw(weaken);
require OOPS::DBOdebug;
require Exporter;
our @ISA = qw(Exporter);
our @EXPORT = qw(dbiconnect dboconnect $pmatch);
our $backends = qr/(?:mysql|pg|sqlite|sqlite2)/i;
our $pmatch = qr/
(?:
[^()]
|
\(
(?:
[^()]
|
\(
(?:
[^()]
|
\(
(?:
[^()]
|
\(
.*?
\)
)*?
\)
)*?
\)
)*?
\)
)*?
/xs;
sub new
{
my ($pkg, %args) = @_;
return bless \%args;
}
sub initial_query_set
{
return '';
}
sub learn_queries
{
my ($dbo, $Q) = @_;
while ($Q =~ /\G\t\t([a-z]\w*):((?:\s+\d+)*)\s*(#.*)?\n/gc) {
my ($qn, $binary_list, $comment) = ($1, $2);
while ($Q =~ /\G\t\t\t(.*)\n/gc) {
$dbo->{queries}{$qn} .= $1."\n";
}
$dbo->{binary_q_list}{$qn} = $binary_list;
$dbo->{debug_q}{$qn} = $comment;
}
}
sub dbiconnect
{
my ($pkg, %a) = @_;
my $args = \%a;
if (ref($pkg) && ! %a) {
$args = $pkg->{args} || $pkg;
}
my $database = $args->{dbi_dsn} || $args->{DBI_DSN} || $args->{database};
my $user = $args->{user} || $args->{username} || $args->{USER} || $args->{USERNAME};
my $password = $args->{pass} || $args->{password} || $args->{PASS} || $args->{PASSWORD};
my $prefix = $args->{table_prefix} || $args->{TABLE_PREFIX} || $ENV{OOPS_PREFIX} || '';
if (! defined($database)) {
if (defined($ENV{OOPS_DSN})) {
$database = $ENV{OOPS_DSN};
} elsif (defined($ENV{DBI_DSN})) {
$database = $ENV{DBI_DSN}
} elsif (defined($ENV{OOPS_DRIVER})) {
$database = "dbi::$ENV{OOPS_DRIVER}";
} elsif (defined($ENV{DBI_DRIVER})) {
$database = "dbi::$ENV{DBI_DRIVER}";
} else {
die "no database specified";
}
}
die "no database specified" unless $database;
die "only mysql, PostgreSQL & SQLite supported"
unless $database =~ /^dbi:($backends)\b/i;
my $dbms = "\L$1";
$user = $user || $ENV{OOPS_USER} || $ENV{DBI_USER};
$password = $password || $ENV{OOPS_PASS} || $ENV{DBI_PASS};
my $dbh;
unless ($args->{no_dbh}) {
my %a = (
Taint => 0,
PrintError => 0,
AutoCommit => 0,
RaiseError => 1,
HandleError => sub { confess(shift) },
);
$dbh = DBI->connect($database, $user, $password, \%a)
or confess "connect to database: $DBI::errstr" unless $dbh;
$dbh->trace($OOPS::debug_dbi) if $OOPS::debug_dbi;
$dbh = OOPS::DBO::DBIdebug->new($dbh)
if $OOPS::debug_queries & 32;
}
require "OOPS/$dbms.pm";
my $tmode = can("OOPS::$dbms", "tmode") || die;
&$tmode(undef, $dbh, $args->{readonly} || 0);
return $dbh unless wantarray;
my $new = can("OOPS::$dbms", "new") || die;
my $dbo = &$new("OOPS::$dbms",
table_prefix => $prefix,
database => $database,
user => $user,
password => $password,
readonly => $args->{readonly},
default_synchronous => $args->{default_syncronous},
dbh => $dbh,
dbms => $dbms,
);
bless $dbo, "OOPS::$dbms";
$dbo->initialize() unless $args->{no_dbh};
return ($dbh, $dbms, $prefix, $dbo);
}
sub dboconnect
{
confess unless @_ % 2 == 1;
my ($pkg, %a) = @_;
my ($dbh, $dbms, $prefix, $dbo) = dbiconnect($pkg, %a);
$dbo->{do_disconnect} = 1;
return $dbo;
}
sub DESTROY
{
my $dbo = shift;
$dbo->disconnect() if $dbo->{do_disconnect};
}
sub clean_query
{
my ($dbo, $query) = @_;
$query =~ s/^\s*#.*//mg;
$query =~ s/#.*debug.*//mg;
1 while $query =~ s/DBO:\S+?\(($pmatch)\)/$1/gs;
$query =~ s/TP_/$dbo->{table_prefix}/g;
if ($query =~ /^\s*:$backends:\s*$/m) {
my ($before, %map) = split(/^\s*:($backends):\s*$/m, $query);
$query = $map{$dbo->{dbms}} || $before;
print "Query selected = $query\n" if $OOPS::debug_queries & 16;
}
$query =~ s/\n/ /g; # mysql query log is easier to debug
return $query;
}
sub query
{
my ($dbo, $q, %args) = @_;
my $query;
my $dbh;
my $sth;
$dbo->query_debug('', $q, %args);
if (($sth = $dbo->{cached_queries}{$q})) {
# great
if ($sth->{Active}) {
print "Query $q was still active\n" if $OOPS::debug_queries;
delete $dbo->{cached_queries}{$q};
return query($dbo, $q, %args);
}
} elsif (($query = $dbo->{queries}{$q})) {
$query = $dbo->clean_query($query);
$dbh = $args{dbh} || $dbo->{dbh};
$sth = $dbh->prepare($query) || confess "prepare $query: ".$dbh->errstr;
$dbo->{cached_queries}{$q} = $sth;
} else {
confess;
}
if (exists $args{execute}) {
my @a = defined($args{execute})
? (ref($args{execute})
? @{$args{execute}}
: $args{execute})
: ();
$sth->execute(@a) || confess("could not execute '$query' with '@a':".$sth->errstr);
}
return $sth;
}
sub adhoc_query
{
my ($dbo, $query, %args) = @_;
my $name = $query;
$name = $1 if $query =~ /^\s*#\s+DBO:name\s+(\S+)/;
$dbo->{queries}{$name} = $query;
$dbo->query($name, %args);
}
sub disconnect
{
my ($dbo) = @_;
return unless $dbo->{dbh};
$dbo->{dbh}->disconnect();
delete $dbo->{dbh};
}
sub commit
{
my $dbo = shift;
confess unless $dbo->{dbh};
$dbo->{dbh}->commit();
}
sub errstr
{
my $dbo = shift;
return $DBI::errstr unless $dbo->{dbh};
return $dbo->{dbh}->errstr;
}
sub dbh
{
my $dbo = shift;
return $dbo->{dbh};
}
sub dbo
{
my $dbo = shift;
return $dbo;
}
sub rollback
{
my $dbo = shift;
$dbo->{dbh}->rollback;
$dbo->{cached_queries} = {};
}
sub rebless
{
my $oops = shift;
}
sub do_forcesave { 0; }
1;