package CBNP::MetaLite; use strict; use vars qw($VERSION @ISA); use Carp ":DEFAULT", "cluck"; # For informative error messages use DBI; use CBNP::Constants ":standard"; use CBNP::DBI::ErrorHandler::Retry; @ISA = (); # Abstract base class $VERSION = '0.01'; # POD-formatted documentation #------------------------------------------------------------------------------- =head1 NAME CBNP::MetaLite - Abstract base class for different brand MetaLite objects =head1 SYNOPSIS # Create the apropriate brand sub-class object, not a MetaLite object directly use CBNP::MetaLite::Oracle; my $metaLite = CBNP::MetaLite::Oracle->new(); # Get a database handle for the requested database my $databaseHandle = $metaLite->getRWHandle("kpath"); # Reserve an ID my $nextID = $metaLite->getNextID($databaseHandle, "kpath.site"); # Create a brand-specific BEGIN...END statement my $transactionString = $metaLite->getAtomicTransactionString(\@commandList); # Get a brand-specific formatted date my $currentDate = $metaLite->getDate(); # getDate() is also okay to call non-objectively # This is the first of only two functions you may call non-objectively my $date = CBNP::MetaLite::getDate(); # This is the second of the only two functions you may call non-objectively my $databaseHandle = CBNP::MetaLite::safeDBIConnect($connectParamRef, $DBIParamRef, $ENVParamRef); =head1 EXAMPLES See synopsis. =head1 DESCRIPTION MetaLite is an abstract base class for different brand MetaLite objects. Only the functions getDate() and safeDBIConnect() are safe to call non-objectively. A primary design consideration is keeping the number of open connections to all the different databases to a minimum. =head1 APPENDIX The following documentation describes the functions in this package =cut #------------------------------------------------------------------------------- =head2 new Usage : Do not use this directly Function : Throws an exception when called, this is backup for incomplete sub-class implementations. Arguments : Example : Do not use this directly Returns : Does not return, throws an exception =cut sub new { my ($classType) = @_; confess("cannot instantiate object of abstract type CBNP::MetaLite"); } #------------------------------------------------------------------------------- =head2 safeDBIConnect Usage : my $databaseHandle = $metaLite->safeDBIConnect($connectParamRef, $DBIParamRef, $ENVParamRef); my $databaseHandle = CBNP::MetaLite::safeDBIConnect($connectParamRef, $DBIParamRef, $ENVParamRef); Function : Attempts use CBNP::DBI::ErrorHandler::Retry to connect to a database. Arguments : Hash reference ($connectParamRef) - connection parameters for preparing for getting a DBI handle. The keys that must be defined in this hash are: dataSource - the string that will be the data source in the DBI connect user - the user name to connect as password - the password for the user Hash reference ($DBIParamRef) - the hash reference that will be passed directly to DBI connect() (see DBI.pm) Hash reference ($ENVParamRef) - name/value pairs for ENV variables that must be set. These normally take the form of $ENV{"Sybase"} = "/sybase"; which would require the key "Sybase" with the value "/sybase". Example : See Usage Returns : Database handle of type "CBNP::DBI::ErrorHandler::Retry" to the specified database =cut sub safeDBIConnect { my ($this, $connectParamRef, $DBIParamRef, $ENVParamRef) = @_; # When called like an ordinary subroutine, all arguments have to be # shifted down one position # Funky way of deciding whether this was called as a class method, or as # an ordinary subroutine # Since you can't call isa() on an unblessed ref, check what the ref is of, # then call UNIVERSAL::isa with the 2 symbols, then cross your fingers. if(!UNIVERSAL::isa(ref($this), "CBNP::MetaLite")) { $ENVParamRef = $DBIParamRef; $DBIParamRef = $connectParamRef; $connectParamRef = $this; } # Make sure all the required parameters in $connectParamRef are usable if(ref($connectParamRef) ne "HASH") { confess("first argument must be a hash reference"); } if(!exists $connectParamRef->{"dataSource"}) { confess("parameter value \"dataSource\" must be defined"); } if(!exists $connectParamRef->{"user"}) { confess("parameter value \"user\" must be defined"); } if(!exists $connectParamRef->{"password"}) { confess("parameter value \"password\" must be defined"); } # Make sure the hash ref that will be passed to DBI for connection is defined # Also check if the "Brand" key exists here if(defined $DBIParamRef) { if(ref($DBIParamRef) ne "HASH" || !exists $DBIParamRef->{"Brand"}) { confess("second parameter must be a hash reference to a hash that " . "contains the key \"Brand\", which is required for creating " . "CBNP::DBI::ErrorHandler::Retry database handles (the type " . "of database handle that MetaLite creates)"); } } # Set ENV for everything passed into this function through $ENVParamRef if(defined $ENVParamRef) { if(ref($ENVParamRef) ne "HASH") { confess("optional third parameter must be a hash reference or undef"); } foreach my $currKey(keys %{$ENVParamRef}) { $ENV{"$currKey"} = $ENVParamRef->{"$currKey"}; } } # Load in the values my $dataSourceString = $connectParamRef->{"dataSource"}; my $userName = $connectParamRef->{"user"}; my $password = $connectParamRef->{"password"}; # print "Connecting with U: \"$userName\"\n" . # "P: \"$password\"\n" . # "DS: \"$dataSourceString\"\n"; my $databaseHandle = CBNP::DBI::ErrorHandler::Retry->connect( $dataSourceString, $userName, $password, $DBIParamRef); return $databaseHandle; } #------------------------------------------------------------------------------- =head2 getDate Usage : my $date = $metaLite->getDate(); my $date = CBNP::MetaLite::getDate(); Function : Creates a database-formatted date (ie "Jan 10 2004 06:22AM"). This function should be overridden whenever a specific brand of database does not handle this format of date. Arguments : Example : See Usage Returns : String - formatted date =cut sub getDate { my ($this) = @_; my $year = `date "+%Y"`; chomp $year; my $month = `date "+%m"`; chomp $month; my $day = `date "+%d"`; chomp $day; my $hour = `date "+%H"`; chomp $hour; my $min = `date "+%M"`; chomp $min; my %monthList = ("01" => "Jan", "02" => "Feb", "03" => "Mar", "04" => "Apr", "05" => "May", "06" => "Jun", "07" => "Jul", "08" => "Aug", "09" => "Sep", "10" => "Oct", "11" => "Nov", "12" => "Dec"); my $hourMeridian = "AM"; if($hour >= 12) { $hour -= 12; $hourMeridian = "PM"; } if($hour == 0) { $hour = 12; } if(length($hour) == 1) { $hour = "0" . $hour; } if(length($min) == 1) { $min = "0" . $min; } my $dateString = $monthList{$month} . " $day $year $hour:$min$hourMeridian"; return $dateString; } #------------------------------------------------------------------------------- =head2 getRWHandle Usage : Do not use this directly Function : Throws an exception when called, this is backup for incomplete sub-class implementations. Arguments : String - database name to connect to as defined in meta_db.db_name Example : See Usage Returns : Does not return, throws an exception The brand-specific implementation of this function should return a read-write handle to the database requested. =cut sub getRWHandle { my ($this, $databaseName) = @_; confess("function getRWHandle() not implemented for class type \"" . ref($this) . "\""); } #------------------------------------------------------------------------------- =head2 getROHandle Usage : Do not use this directly Function : Throws an exception when called, this is backup for incomplete sub-class implementations. Arguments : String - database name to connect to as defined in meta_db.db_name Example : See Usage Returns : Does not return, throws an exception The brand-specific implementation of this function should return a read-only handle to the database requested. =cut sub getROHandle { my ($this, $databaseName) = @_; confess("function getROHandle() not implemented for class type \"" . ref($this) . "\""); } #------------------------------------------------------------------------------- =head2 getNextID Usage : Do not use this directly Function : Throws an exception when called, this is backup for incomplete sub-class implementations. The brand-specific implementation of this function should return the next_id value for a requested database/table pair. This serves the purpose of reserving the ID (or batch of IDs) for use. Arguments : DBI Handle - open database handle to the database that you want the next_id for String - the database name and table name, separated by a period Integer - how many ID's to reserve (default is 1 ID) Example : See Usage Returns : Does not return, throws an exception Brand-specific implementation should return the lowest next_id of the requested batch =cut sub getNextID { my ($this, $databaseHandle, $databaseAndTableString, $count) = @_; confess("function getNextID() not implemented for class type \"" . ref($this) . "\""); } #------------------------------------------------------------------------------- =head2 getAtomicTransactionString Usage : Do not use this directly Function : Throws an exception when called, this is backup for incomplete sub-class implementations. The brand-specific implementation of this function should construct a "BEGIN...END" transaction string, using an arrayref as the only parameter, where each element in the array is a complete SQL statement (no semicolons or newlines). Arguments : Arrayref - individual SQL statements to make into a BEGIN...END SQL statement Example : See Usage Returns : Does not return, throws an exception Brand-specific implementation should return the complete "BEGIN...END" SQL statement as a string =cut sub getAtomicTransactionString { my ($this, $statementListRef) = @_; confess("function getAtomicTransactionString() not implemented for class type \"" . ref($this) . "\""); } #------------------------------------------------------------------------------- =head2 getMetaTableID Usage : Do not use this directly Function : Uses memoization to return a meta_table_id based on the string specification "database.table". Arguments : String - database and table name, separated by a period "db.table" Example : See Usage Returns : Integer - the meta_table_id for the specified table =cut sub getMetaTableID { my ($this, $databaseAndTableString) = @_; if(!defined $databaseAndTableString) { confess("parameter must be in the form \"database.table\""); } # Normalize (lower-case db and table names) $databaseAndTableString = lc($databaseAndTableString); # Make sure the hashref is in this object because we're going to be asking # for data from it. if(!exists $this->{"d_metaTableCache"}) { $this->{"d_metaTableCache"} = {}; } # If the value hasn't been looked up yet, then look it up if(!exists $this->{"d_metaTableCache"}->{$databaseAndTableString}) { # Split the db name from the table name my ($databaseName, $tableName) = ($databaseAndTableString =~ /(\w+)\.(\w+)/); if(!defined $databaseName || !defined $tableName) { confess("parameter is in improper form, shoud be \"database.table\""); } # Request a handle to meta my $metaHandle = $this->getMetaHandle(); # Get the meta table ID my $tempSQL = "SELECT meta_table_id FROM meta_table WHERE table_name = " . $metaHandle->quote($tableName) . " AND meta_db_id = " . "(SELECT meta_db_id FROM meta_db WHERE db_name = " . $metaHandle->quote($databaseName) . ")"; my ($metaTableID) = $metaHandle->selectrow_array_exists($tempSQL); # Store the ID in the cache hash $this->{"d_metaTableCache"}->{$databaseAndTableString} = $metaTableID; # Clean up $metaHandle->disconnect(); } return $this->{"d_metaTableCache"}->{$databaseAndTableString}; } #------------------------------------------------------------------------------- =head2 getMetaInfo Usage : Do not use this directly Function : Throws an exception when called, this is backup for incomplete sub-class implementations Arguments : String - name of the database as defined in meta_db.db_nameS String - read mode ("SELECT" or "ALL" currently) Example : See Usage Returns : Does not return, throws an exception The brand-specific implementation should return a hash reference which contains the following keys: server - server name (bbrp.llnl.gov) user - user name password - user's password =cut sub getMetaInfo { my ($this, $databaseName, $readMode) = @_; confess("function getMetaInfo not implemented for class type \"" . ref($this) . "\""); } #------------------------------------------------------------------------------- =head2 getMetaHandle Usage : Do not use this directly Function : Throws an exception when called, this is backup for incomplete sub-class implementations. Arguments : Example : See Usage Returns : Does not return, throws an exception The brand-specific implementation of this function should return a DBI handle to the appropriate meta database =cut sub getMetaHandle { my ($this) = @_; confess("function getMetaHandle() not implemented for class type \"" . ref($this) . "\""); } 1; # Got one? __END__ =head1 AUTHOR Clinton Torres (clinton.torres@llnl.gov) =head1 SEE ALSO =cut