1 package CXGN
::DB
::Connection
;
5 use Carp qw
/cluck croak carp confess/;
9 use List
::MoreUtils qw
/ any /;
12 use base qw
/Class::Accessor::Fast/;
15 #list of method names that, when called on this object,
16 #will be forwarded to the enclosed database handle
69 foreach my $forward_method (@dbh_methods) {
71 *{$forward_method} = sub { shift->_dbh->$forward_method(@_) };
75 __PACKAGE__
->mk_accessors( '_dbname',
86 #note that dbtype is an actual function defined below
92 CXGN::DB::Connection - connect to CXGN databases
97 my $dbh = CXGN::DB::Connection->new();
98 $dbh->do("SELECT 'BLAH'");
102 # as part of an object
103 use base qw/CXGN::DB::Connection/;
104 $self->do("SELECT 'BLAH'");
108 This module can be used in two ways: it is a database handle ($dbh)
109 that can be used directly; alternatively, objects such as database APIs can
110 inherit from this by adding 'CXGN::DB::Connection' to their @ISA, and thus
111 become database handles themselves (see also L<Ima::DBI>, which allows
112 you to share a single database handle between all instances of a class).
114 To connect to the database, this object needs to figure out values
115 for a number of parameters (listed below in the documentation for new() ).
116 Any one of these parameters can be set in 4 different places:
117 1.) As arguments to the new() method when the object is created.
118 2.) As environment variables (e.g. $ENV{DBUSER})
119 3.) As parameters in the CXGN::Configuration system (e.g. $conf->get_conf('dbuser') )
120 4.) From the hardcoded default inside this module
122 Arguments to the new() method override environment variables, which override
123 CXGN::Configuration parameters, which override the hardcoded defaults. Get it?
124 The hardcoded defaults are listed below in the documentation for new().
126 Example of setting environment variables for a database connection (using a bash
129 user@box:~$ export $DBUSER=somebodyelse
130 user@box:~$ export $DBPASS=lemmein
131 user@box:~$ ./my_script_that_uses_db_connection.pl
133 Will specify a user and password for the script to use.
137 If the CXGN_DB_CONNECTION_TRACE_CONNECTIONS environment variable is
138 set, will append backtraces of all database connections to the file
139 /tmp/cxgn_db_connections.log
141 Also, CXGN::DB::Connection supports the standard DBI tracing
142 facilities. See L<DBI> perldoc.
148 Desc: Connects to the database and returns a new Connection object
149 Args: parameters hash ref as
152 config => hashref of configuration parameters (from a config object or similar)
154 OPTIONAL (override parameters in config of the same name):
155 dbname => name of the database; defaults to 'cxgn'; unused for MySQL
156 dbschema => name of schema you want to connect to; defaults to 'sgn'
157 dbtype => type of database - 'Pg' or 'mysql'; defaults to 'Pg'
158 dbuser => username for the connection; defaults to 'web_usr'
159 dbpass => password for the connection; defaults to nothing
160 dbargs => DBI connection params, merged with the default, which are explained below,
161 dbhost => host to connect to, default 'db.sgn.cornell.edu',
162 dbbranch => the database "branch" to use, default 'devel' unless you are configured as a production website, in which case it would default to 'production'
164 all parameters in the hash are optional as well
165 Ret: new CXGN::DB::Connection object
166 Side Effects: sets up the internal state of the CXGN::DB::Connection object
168 my $dbh = CXGN::DB::Connection->new();
174 If the environment variable $ENV{MOD_PERL} is set (which would be
175 the case if you are running under mod_perl, AutoCommit defaults to 1, that is, ON. If
176 $ENV{MOD_PERL} is not set, which would probably be the case if you
177 are running in some other environment (like in a shell), AutoCommit is
178 defaults to 0, which is OFF. For more on what AutoCommit is and why you
179 need to be careful with it, see the documentation of L<DBI> and
182 Note that DBI handle options (AutoCommit, RaiseError, etc) are also
183 merged, with the same order of precedence as the DB::Connection options
184 (dbname, dbpass, etc).
188 =head2 new_no_connect
190 Desc: Same as above, except does not connect to the database.
191 Used by things that want to use the connection parameters for
197 my ($class, $db, $p) = @_;
198 my $self = bless {},$class;
200 #carp "WARNING: CXGN::DB::Connection is deprecated"
201 # unless $ENV{TESTING_CXGN_DB_CONNECTION};
203 # This is a little gross, but it looks weird to have to call new
204 # like this when all you want to customize is the dbuser:
206 # new("sgn", { 'dbuser' => 'bob' });
208 # According to Rob, this usage was meant to match some deprecated interface.
209 # Anyhow, if $db is a ref, then use it as a hash potentially containing
210 # all the connection parameters. -- Marty, 2005 July 26.
211 if (ref ($db)) { # The first argument was a hash ref.
212 if (ref $p) { # Something's wrong if TWO hash refs were passed
213 croak
("invalid arguments to CXGN::DB::Connection constructor");
215 $p = $db; # The first argument was a hash ref, so call it $p.
217 } else { # The first argument is the dbschema
218 $p->{dbschema
} = $db;
221 my $autocommit_default = 0;
222 if( any
{ defined $ENV{$_} } qw
| MOD_PERL GATEWAY_INTERFACE CATALYST_ENGINE SERVER_SOFTWARE PATH_INFO HTTP_USER_AGENT CONTENT_LENGTH WEB_PROJECT_NAME
| ) {
223 $autocommit_default = 1; #if we are running in the website, run with autocommit on. otherwise, don't
226 # process the config object we were passed
227 my $conf = delete $p->{config
};
229 # if we got a config object, check it for validity
230 if( Scalar
::Util
::blessed
($conf) && $conf->isa('Bio::GMOD::DB::Config') ) {
231 $conf = { dbhost
=> $conf->host,
232 dbport
=> $conf->port,
233 dbuser
=> $conf->user,
234 dbpass
=> $conf->password,
235 dbytpe
=> $conf->driver,
236 dbname
=> $conf->name,
241 or croak
"invalid config, must be a hashref like { varname => value, }";
244 # if we didn't get a config object, get the configuration from SGN if running under SGN, otherwise die
245 if( defined $ENV{PROJECT_NAME
} && $ENV{PROJECT_NAME
} eq 'SGN' ) {
247 require CatalystX
::GlobalContext
;
248 CatalystX
::GlobalContext
->import('$c');
249 $c or confess
"Catalyst context object not found. Are we actually running under Catalyst?";
252 require CXGN
::Config
;
253 $conf = CXGN
::Config
->load;
256 $self->_conf( $conf );
258 my $dbbranch_default = exists $conf->{"production_server"} && $conf->{"production_server"} ?
"production" : "devel";
260 if( $p->{dbschema
} ) {
261 carp
"WARNING: dbschema argument to CXGN::DB::Connection is ignored. Set the dbsearchpath configuration variable instead";
264 # warn "branch: " . $dbbranch_default;
266 #list of valid args to pass in the params hash, and defaults and auxiliary configuration sources for them.
267 my @args = (# hash key, environment var name, configuration var name, hardcoded default value
268 ['dbhost' , 'DBHOST' , 'dbhost' , 'db.sgn.cornell.edu' ],
269 ['dbport' , 'DBPORT' , 'dbport' , '' ],
270 ['dbname' , 'DBNAME' , 'dbname' , 'cxgn' ],
271 ['dbuser' , 'DBUSER' , 'dbuser' , 'web_usr' ],
272 ['dbpass' , 'DBPASS' , 'dbpass' , '' ],
273 ['dbtype' , 'DBTYPE' , 'dbtype' , 'Pg' ],
274 ['dbschema', 'DBSCHEMA', 'dbschema', 'sgn' ],
275 ['dbbranch', 'DBBRANCH', 'dbbranch', $dbbranch_default ], #see above for default
276 ['dbargs' , undef , undef , {RaiseError
=>1,AutoCommit
=>$autocommit_default}], #see above for default
279 my %valid_argnames = map {$_->[0],1} @args;
281 #copy our args from the parameters hash into our object
282 foreach my $pkey (keys %$p) {
283 unless ($valid_argnames{$pkey}) {
284 warn Carp
::longmess
("Invalid parameter '$pkey' passed to CXGN::DB::Connection argument hash");
287 my $upkey = "_$pkey";
288 $self->$upkey( $p->{$pkey} );
291 #check for environment variables for things that weren't set from
292 #the params hash, and if there is still no value, set it to the
293 #conf object's value, and if there is still no value, set it to the
295 foreach my $arg (@args) {
296 my ($argname,$envname,$confname,$default) = @
$arg;
297 if( $argname eq 'dbargs' ) { #merge dbargs specially
299 #only get args from either what you passed in, or the hardcoded defaults
300 foreach my $hash ( $default, $self->_dbargs ) {
301 while( my($key,$val) = each %$hash ) {
302 $merged{$key} = $val;
305 $self->_dbargs( \
%merged );
308 $argname = "_$argname"; #these are private methods
310 # If the $argname field isn't filled in,
311 # then if the policy allows for an environment
312 # variable, a conf object key, or a default,
313 # try those in succession.
314 $self->$argname( $ENV{$envname} )
315 if defined $envname && ! defined $self->$argname;
317 $self->$argname( $conf->{$confname} )
318 if defined $confname && ! defined $self->$argname && exists $conf->{$confname};
320 $self->$argname( $default )
321 if defined $default && ! defined $self->$argname;
325 ## Validation and such.
327 # Now set up our derived connection params based on what kind of
328 # database we're connecting to
329 if( $self->_dbtype eq 'Pg' ) {
330 # The following line adds 1 optional argument to the dbargs hash, but
331 # no DBD driver is expected to use this key: it's there only so that
332 # Apache::DBI will distinguish DBI handles. If some day in the future
333 # DBD::Pg does support schemas, you might as well change this argument,
334 # though leaving it here for Apache::DBI to use shouldn't hurt, either.
335 # update: according to the DBI docs, if your parameter is prefixed by
336 # 'private_', the DB driver will ignore it
337 $self->_dbargs->{private_cxgn_schema
} = $self->_dbschema;
338 my $dsn = "dbi:Pg:".join(';',
339 $self->_dbname ?
"dbname=".$self->_dbname : (),
340 $self->_dbhost ?
"host=".$self->_dbhost : (),
341 $self->_dbport ?
"port=".$self->_dbport : (),
346 elsif( $self->_dbtype eq 'mysql' ) {
347 if ($self->_dbschema) {
348 $self->_dsn( "dbi:mysql:host=".$self->_dbhost.";database=".$self->_dbschema.';port='.$self->_dbport );
349 } elsif ($self->_dbname) {
350 $self->_dsn( "dbi:mysql:host=".$self->_dbhost.";database=".$self->_dbname.';port='.$self->_dbport );
352 croak
"Unknown CXGN::DB::Connection database name or schema for dbtype '".$self->_dbtype."'";
359 sub _compact_backtrace
{
360 return join '/', map {join(':',(caller($_))[0,2])} 1..3;
363 my $debug = CXGN
::Debug
->new;
367 my $self = $class->new_no_connect(@_);
369 # Now connect to a DB, dying from the caller's perspective
370 eval { $self->_dbh( DBI
->connect($self->get_connection_parameters) ) }; croak
$@
if $@
;
371 $self->trace_msg('CXGN_TRACE | '._compact_backtrace
().' | '.__PACKAGE__
."::new | $self | ".$self->_dbh."\n",1);
373 #generate the search path
374 if ( $self->_dbtype eq 'Pg' ) {
375 my $conf = $self->_conf;
376 my $configured_search_path =
377 Scalar
::Util
::blessed
($conf)
378 ?
$conf->get_conf('dbsearchpath')
379 : $conf->{dbsearchpath
};
381 $configured_search_path
382 or croak
'no dbsearchpath conf variable set! you must set one!';
384 my @searchpath = ref $configured_search_path
385 ? @
$configured_search_path
386 : split /\s*,\s*/, $configured_search_path;
388 push @searchpath, 'tsearch2' unless $self->_dbh->{pg_server_version
} >= 80300;
389 $self->do("SET search_path TO ".join(',',@searchpath));
393 if ( $debug->get_debug || $ENV{CXGN_DB_CONNECTION_TRACE_CONNECTIONS
}) {
394 my $trace_str = join '',map {"$_\n"}
396 "# === DB::Connection parameters ===",
397 "# dbhost: " . $self->_dbhost,
398 "# dbport: " . $self->_dbport,
399 "# dbname: " . $self->_dbname,
400 "# dbuser: " . $self->_dbuser,
401 "# dbtype: " . $self->_dbtype,
402 "# dbschema: " . $self->_dbschema,
403 "# dbbranch: " . $self->_dbbranch,
404 "# searchpath: " . $self->search_path,
407 "# $_ => ".$self->_dbargs->{$_}
408 } keys %{$self->_dbargs}
410 "# === End of DB::Connection parameters ==="
413 $debug->debug($trace_str);
414 if ( $ENV{CXGN_DB_CONNECTION_TRACE_CONNECTIONS
} ) {
416 open STDERR_SAVE
, ">&STDERR" or die "$! saving STDERR";
417 open STDERR
, ">>", '/tmp/cxgn_db_connections.log' or die "run3(): $! redirecting STDERR";
419 open STDERR
, '>&', \
*STDERR_SAVE
;
429 # Ret : a DBI database handle for this connection
432 # my $dbconn = CXGN::DB::Connection->new
433 # $dbconn->dbh->do('delete from seqread');
438 carp __PACKAGE__
.": the dbh() method is deprecated. Just call dbh methods on the CXGN::DB::Connection object instead.";
443 =head2 get_actual_dbh
445 Usage: my $dbh = $self->get_actual_dbh()
446 Desc: return the actual $dbh object
447 Ret: $dbh, a database connectio object
458 =head2 get_connection_parameters
460 Desc: get connection parameters you can use with your own DBI::connect call.
461 Some things (like some CPAN's Class::DBI) seem to have a burning need
462 to make DBI connections themselves. You can satisfy them using the
463 parameters you get from this.
465 Ret : list of (dsn, db user, db password, DBI connection arguments)
469 sub get_connection_parameters
{
471 return ( $self->_dsn,
480 Desc: get the database type set on this connection
482 Ret : the dbtype set on this connection thingy
483 The dbtype will be a string containing either
488 sub dbtype
{ shift->_dbtype } #read-only
494 $self->{dbtype
} = $newtype;
497 my %valid_dbtypes = ( Pg
=> 1, mysql
=> 1 );
499 !$self->{dbtype
} || $valid_dbtypes{ $self->{dbtype
} }
500 or die "Invalid dbtype '$self->{dbtype}'";
502 return $self->{dbtype
};
507 Usage: my $n = $dbc->dbname
508 Ret : the name of the database we're currently connected to
514 sub dbname
{ shift->_dbname } #keep this read-only
518 Usage: my $n = $dbc->dbname
519 Ret : the name of the db user for this connection
525 sub dbuser
{ shift->_dbuser } #keep this read-only
529 Usage: my $n = $dbc->dbpass
530 Ret : the password used for this connection
536 sub dbpass
{ shift->_dbpass } #keep this read-only
540 Usage: my $host = $dbc->dbhost
542 Ret : the hostname of the database server
549 sub dbhost
{ shift->_dbhost } #read-only
553 Usage: my $port = $dbc->dbport
555 Ret : the port on the database server
562 sub dbport
{ shift->_dbport } #read-only
566 Usage: my $branch = $dbc->dbbranch
568 Ret : the name of the database branch we're using,
569 usually either 'devel' or 'production'
576 sub dbbranch
{ shift->_dbbranch } #keeping this read-only
580 Desc: get or set the value of a DBI::db parameter
581 Args: name of parameter to work with, (optional) new value to set it to
582 Ret : new value of DBI::db parameter
583 Side Effects: sets the new value of the parameter in the internal state of
587 my $dbconn = CXGN::DB::Connection->new;
588 $dbconn->dbh_param( AutoCommit => 0 );
589 $dbconn->dbh_param( PrintError => 0 );
590 $dbconn->dbh_param( RaiseError => 1 );
595 my ($self,$paramname,$newvalue) = @_;
597 if( defined($newvalue) ) { #set a new value if given
598 $self->_dbh->{$paramname} = $newvalue;
601 return $self->_dbh->{$paramname};
605 #disconnect the database handle after we're done
606 #and call the DESTROY methods of any parent classes that have them
609 #warn __PACKAGE__."(pid $PID): destroy called on dbc $self\n";
611 return unless $self->_dbh;
612 $self->trace_msg('CXGN_TRACE | '._compact_backtrace
().' | '.__PACKAGE__
."::DESTROY | $self | ".$self->_dbh."\n",1);
614 # unless( $self->dbh_param('InactiveDestroy') ){
615 # #warn "pid $PID disconnecting dbh ".$self->_dbh."\n";
616 # #print a warning in the DBI trace when it's enabled
617 # $self->disconnect(42);
618 # $self->_dbh->DESTROY;
621 # return parricide($self,our @ISA);
624 =head2 qualify_schema
626 Desc: Get a fully-qualified schema name for the connection object or
628 Args: Nothing, or a schema basename.
629 Ret : Fully-qualified schema.
633 my $dbconn = CXGN::DB::Connection->new({dbschema=>"genomic", dbbranch=>"production"});
634 my $qualified_schema_name=$dbconn->qualify_schema; # Returns "genomic"
635 my $qualified_sgn_schema_name=$dbconn->qualify_schema("sgn"); # Returns "sgn"
637 my $dbconn = CXGN::DB::Connection->new({dbschema=>"genomic", dbbranch=>"devel"});
638 my $qualified_schema_name=$dbconn->qualify_schema; # Returns "genomic_dev"
639 my $qualified_sgn_schema_name=$dbconn->qualify_schema("sgn"); # Returns "sgn_dev"
641 Note: DO NOT hard-code the values this method returns in your code.
642 We make no guarantees that development/production schemas will keep
643 their names in future.
648 my ($self, $schema_basename, $return_base_table) = @_;
649 carp
"qualify_schema method is deprecated";
650 return $schema_basename;
655 For migrating our tables out of the base schemas, factor
656 out all "_bt" from code
661 my ($self, $schema) = @_;
662 carp
"base_schema method is deprecated";
668 Desc: Get the databases current SEARCH_PATH parameter (or a snarky
669 message, if the database is MySQL.
675 # This is your program
676 my $sp = $dbh->search_path;
686 if ($self->_dbtype =~ 'mysql') {
687 return ("You're using MySQL. Nnyeeehhh.");
689 my ($sp) = $self->selectrow_array('SHOW SEARCH_PATH');
694 =head2 add_search_path
696 Add a schema to the search path, if it is not already there.
697 One or many search paths may be added at once.
698 Ex: $dbh->add_search_path(qw/ sgn sgn_people /);
702 sub add_search_path
{
704 my ($current_string) = $self->selectrow_array('SHOW SEARCH_PATH');
705 my @current = split ",", $current_string;
707 @current = grep { /\w/ } @current; #avoid null items in array
708 s/\s//g foreach @current; #trim trailing spaces
709 my $search_paths = {};
710 $search_paths->{$_} = 1 foreach (@current);
711 my $new_string = join ",", keys %$search_paths;
712 my $update_q = "SET SEARCH_PATH=$new_string";
713 $self->do($update_q);
716 =head2 last_insert_id
718 Desc: Return the last auto-incremented primary key inserted into
719 a table in the current connection. Postgres ONLY!
720 Args: A table name, an optional schema name (eventually perhaps the table
721 name can be optional).
722 Ret : The last insert id, an integer.
724 Warning: in case there is no sequence found to be the default value
725 for the primary key column of the table argument, this method
726 DIEs. You should only ever be using this inside a transaction
727 eval block, and therefore should be checking the return from
731 $dbh->do("INSERT INTO mytable (mytable_id, foo, bar) VALUES (DEFAULT, 1, 'two')");
732 my $id = $dbh->last_insert_id("mytable")
734 -| 12345 # just an example
739 my ($self, $table, $schema) = @_;
741 # warning before the die gets the message through to loading scripts.
742 warn "you forgot the table name argument to last_insert_id\n" unless $table;
745 $schema ||= $self->_dbschema;
746 # This query needs work. For one thing, we're banking on the split_part/replace
747 # munging things correctly, which is dubious. It'd be better to actually
748 # figure out the name of the sequence associated with the default on a primary
749 # key column, but I don't know how to do that.
750 my ($seq) = $self->selectrow_array("SELECT split_part(replace(adsrc, 'nextval(''', ''), '''::', 1)
751 FROM pg_class t, -- tables
752 pg_attribute a, -- attributes (columns)
753 pg_attrdef d, -- attribute defaults
754 pg_namespace n, -- schemas (called namespaces inside Pg)
755 pg_constraint o -- constraints
756 WHERE t.relkind='r' -- select only tables
757 AND n.oid=t.relnamespace -- joining tables to schemas
758 AND a.attrelid=t.oid -- joining attributes to tables
759 AND d.adnum=a.attnum -- joining defaults to attributes
760 AND d.adrelid=t.oid -- joining defaults to tables
761 AND o.conrelid=t.oid -- joining constraints with tables
762 AND o.contype='p' -- selecting only pkey constraints
763 AND array_upper(o.conkey, 1)=1 -- selecting only single-column constraints (serial numbering can only apply to one column)
764 AND o.conkey[1]=a.attnum -- joining constraint column numbers with attribute column numbers
765 AND adsrc LIKE 'nextval%' -- select only defaults that look like nextval of some sequence
766 AND t.relname = '$table' -- select only tables whose name is the table argument
767 AND n.nspname = '$schema' -- select only schemas whose name is the schema argument");
768 $seq or die"No sequence name found for table '$table' and schema '$schema' using qualified schema name '$schema'.\n";
769 my ($id) = $self->selectrow_array("SELECT currval('$schema.$seq')")
770 or die "No id found for table '$table' and schema '$schema' using qualified schema name '$schema' and sequence name '$seq'.\n";
778 =head1 DBI database handle methods
780 All methods that can be used on a DBI database handle can also be
781 used on a CXGN::DB::Connection object. They will be transparently
782 forwarded to this object's enclosed database handle.
788 Usage: $dbconn->disconnect(42);
789 #if you don't pass 42 here, it will print a dire warning
790 Desc : Disconnect this database connection. To prevent people
791 doing this by accident, you must give this an argument
792 of 42, or it will print a _dire_ warning. Really, very dire.
793 Ret : true if the disconnection was successful
794 Args : (optional) the number 42
795 Side Effects: disconnects from the database
799 Notes: This is here because a lot of code uses shared database
800 handles, and erroneously disconnecting one can have great
801 potential for causing far-away code to die a gruesome death.
805 #to alter which methods are forwarded, edit the array @dbh_methods
806 #at the top of this file
808 #disconnect is not directly forwarded, because we want to catch
809 #spurious disconnects, because they may be buried somewhere
813 return $self->_dbh->disconnect;
820 DEPRECATED. This method no longer does anything, this module just uses CXGN::Debug::debug.
823 # Usage: CXGN::DB::Connection->verbose(1);
824 # Desc : get/set the verbosity level of CXGN::DB::Connection objects.
825 # defaults to 0 in web server, 1 otherwise.
826 # Ret : currently set verbosity level
827 # Args : new verbosity level
828 # Side Effects: sets the verbosity level for all CXGN::DB::Connection objects
834 carp
"CXGN::DB::Connection::verbose no longer does anything, might as well remove this invocation";
840 if($dbh eq 'CXGN::DB::Connection')
842 warn "You sent in the string 'CXGN::DB::Connection'. Did you call this function with a '->' instead of a '::'?";
847 warn "'$dbh' is not a reference";
850 if(ref($dbh) eq "ARRAY" or ref($dbh) eq "HASH")
852 warn "'$dbh' is not an object reference";
855 unless( $dbh->can('selectall_arrayref') )
857 warn "'$dbh' is a reference but does not look like a valid dbh";
865 This module is part of the SGN/CXGN codebase and is distributed under
866 the same terms that it is. If you're confused about the license or did
867 not receive one, please email us at sgn-feedback@sgn.cornell.edu.
871 Written by the SGN crew. The first person to have her grubby mitts
872 on this was Beth. Then Rob. Then Marty. Then Rob again. And so on.
876 L<DBI>, L<Apache::DBI>, L<Ima::DBI>, L<Class::DBI>