# Copyright (C) 2005 Stanislav Sinyagin # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. # $Id: SQL.pm,v 1.1 2010-12-27 00:03:38 ivan Exp $ # Stanislav Sinyagin # Package for RDBMS communication management in Torrus # Classes should inherit Torrus::SQL and execute Torrus::SQL->new(), # and then use methods of DBIx::Abstract. package Torrus::SQL; use strict; use DBI; use DBIx::Abstract; use DBIx::Sequence; use Torrus::Log; my %connectionArgsCache; # Obtain connection attributes for particular class and object subtype. # The attributes are defined in torrus-siteconfig.pl, in a hash # %Torrus::SQL::connections. # For a given Perl class and an optional subtype, # the connection attributes are derived in the following order: # 'Default', 'Default/[subtype]', '[Class]', '[Class]/[subtype]', # 'All/[subtype]'. # For a simple setup, the default attributes are usually defined for # 'Default' key. # The key attributes are: 'dsn', 'username', and 'password'. # Returns a hash reference with the same keys. sub getConnectionArgs { my $class = shift; my $objClass = shift; my $subtype = shift; my $cachekey = $objClass . ( defined( $subtype )? '/'.$subtype : ''); if( defined( $connectionArgsCache{$cachekey} ) ) { return $connectionArgsCache{$cachekey}; } my @lookup = ('Default'); if( defined( $subtype ) ) { push( @lookup, 'Default/' . $subtype ); } push( @lookup, $objClass ); if( defined( $subtype ) ) { push( @lookup, $objClass . '/' . $subtype, 'All/' . $subtype ); } my $ret = {}; foreach my $attr ( 'dsn', 'username', 'password' ) { my $val; foreach my $key ( @lookup ) { if( defined( $Torrus::SQL::connections{$key} ) ) { if( defined( $Torrus::SQL::connections{$key}{$attr} ) ) { $val = $Torrus::SQL::connections{$key}{$attr}; } } } if( not defined( $val ) ) { die('Undefined attribute in %Torrus::SQL::connections: ' . $attr); } $ret->{$attr} = $val; } $connectionArgsCache{$cachekey} = $ret; return $ret; } my %dbhPool; # For those who want direct DBI manipulation, simply call # Class->dbh($subtype) with optional subtype. Then you don't use # any other methods of Torrus::SQL. sub dbh { my $class = shift; my $subtype = shift; my $attrs = Torrus::SQL->getConnectionArgs( $class, $subtype ); my $poolkey = $attrs->{'dsn'} . '//' . $attrs->{'username'} . '//' . $attrs->{'password'}; my $dbh; if( exists( $dbhPool{$poolkey} ) ) { $dbh = $dbhPool{$poolkey}; if( not $dbh->ping() ) { $dbh = undef; delete $dbhPool{$poolkey}; } } if( not defined( $dbh ) ) { $dbh = FS::DBI->connect( $attrs->{'dsn'}, $attrs->{'username'}, $attrs->{'password'}, { 'PrintError' => 0, 'AutoCommit' => 0, } ); if( not defined( $dbh ) ) { Error('Error connecting to DBI source ' . $attrs->{'dsn'} . ': ' . $FS::DBI::errstr); } else { $dbhPool{$poolkey} = $dbh; } } return $dbh; } END { foreach my $dbh ( values %dbhPool ) { $dbh->disconnect(); } } sub new { my $class = shift; my $subtype = shift; my $self = {}; $self->{'dbh'} = $class->dbh( $subtype ); if( not defined( $self->{'dbh'} ) ) { return undef; } $self->{'sql'} = DBIx::Abstract->connect( $self->{'dbh'} ); $self->{'subtype'} = $subtype; $self->{'classname'} = $class; bless ($self, $class); return $self; } sub sequence { my $self = shift; if( not defined( $self->{'sequence'} ) ) { my $attrs = Torrus::SQL->getConnectionArgs( $self->{'classname'}, $self->{'subtype'} ); $self->{'sequence'} = DBIx::Sequence->new({ dbh => $self->{'dbh'}, allow_id_reuse => 1 }); } return $self->{'sequence'}; } sub sequenceNext { my $self = shift; return $self->sequence()->Next($self->{'classname'}); } sub fetchall { my $self = shift; my $columns = shift; my $ret = []; while( defined( my $row = $self->{'sql'}->fetchrow_arrayref() ) ) { my $retrecord = {}; my $i = 0; foreach my $col ( @{$columns} ) { $retrecord->{$col} = $row->[$i++]; } push( @{$ret}, $retrecord ); } return $ret; } 1; # Local Variables: # mode: perl # indent-tabs-mode: nil # perl-indent-level: 4 # End: