# $Id$ =pod =head1 NAME - DBTestHarness.pm =head1 SYNOPSIS # Add test dir to lib search path use lib 't'; use DBTestHarness; my $harness = DBTestHarness->new(); # Load some data into the db $ens_test->do_sql_file("some_data.sql"); # Get an Overlap db object for the test db my $db = $harness->db(); =head1 DESCRIPTION This is a direct copy-and-paste from the Ensembl EnsTestDB system. It provides an encapsulation of creating, loading and dropping databases for testing =head1 METHODS =cut package DBTestHarness; use strict; use Sys::Hostname 'hostname'; use DBI; use Carp; use Bio::DB::BioDB; use Bio::DB::SimpleDBContext; #Package variable for unique database name my $counter=0; # Default settings as a hash my $dflt = { 'driver' => 'mysql', 'host' => 'localhost', 'user' => 'root', 'port' => undef, 'password' => '', 'schema_sql' => ['../biosql-schema/sql/biosqldb-mysql.sql'], 'database' => 'biosql', 'module' => 'Bio::DB::BioSQL::DBAdaptor' }; # This is a list of possible entries in the config # file "DBHarness.conf" my %known_field = map {$_, 1} qw( driver host user port password schema_sql dbname database module ); sub new { my( $pkg,$db ) = @_; $counter++; my $self; confess "Must provide db, no default any more" unless $db; # Get config from file, or use default values if( $db eq 'biosql' ) { $self = do 'DBHarness.biosql.conf'; } elsif ( $db eq 'markerdb' ) { $self = do 'DBHarness.markerdb.conf'; $self->{"schema_sql"} = ['./sql/markerdb-mysql.sql'] unless $self->{"schema_sql"}; } else { confess "Don't know about db $db : are you sure you meant to say $db?"; } foreach my $f (keys %$self) { confess "Unknown config field: '$f'" unless $known_field{$f}; } bless $self, $pkg; $self->create_db() unless exists($self->{"dbname"}); return $self; } sub driver { my( $self, $value ) = @_; if ($value) { $self->{'driver'} = $value; } return $self->{'driver'} || confess "driver not set"; } sub host { my( $self, $value ) = @_; if ($value) { $self->{'host'} = $value; } return $self->{'host'}; } sub user { my( $self, $value ) = @_; if ($value) { $self->{'user'} = $value; } return $self->{'user'}; } sub port { my( $self, $value ) = @_; if ($value) { $self->{'port'} = $value; } return $self->{'port'}; } sub password { my( $self, $value ) = @_; if ($value) { $self->{'password'} = $value; } return $self->{'password'}; } sub schema_sql { my( $self, $value ) = @_; if ($value) { push(@{$self->{'schema_sql'}}, $value); } return $self->{'schema_sql'} || confess "schema_sql not set"; } sub dbname { my( $self, $value ) = @_; if($value && (! exists($self->{'dbname'}))) { $self->{'dbname'} = $value; } $self->{'dbname'} = $self->_create_db_name() unless exists($self->{'dbname'}); return $self->{'dbname'}; } sub database { my( $self, $value ) = @_; if($value && (! exists($self->{'database'}))) { $self->{'database'} = $value; } return $self->{'database'}; } # convenience method: by calling it, you get the name of the database, # which you can cut-n-paste into another window for doing some mysql # stuff interactively sub pause { my ($self) = @_; my $db = $self->{'_dbname'}; print STDERR "pausing to inspect database; name of database is: $db\n"; print STDERR "press ^D to continue\n"; while(<>) { 1; } } sub module { my ($self, $value) = @_; $self->{'module'} = $value if ($value); return $self->{'module'}; } sub _create_db_name { my( $self ) = @_; my $host = hostname(); my $db_name = "_test_db_${host}_$$".$counter; $db_name =~ s{\W}{_}g; return $db_name; } sub create_db { my( $self ) = @_; ### FIXME: not portable between different drivers my $locator = 'dbi:'. $self->driver .':host='. $self->host .';'; if ($self->driver eq "Pg") { # HACK! with DBD::Pg we *must* connect to a db $locator = 'dbi:Pg:dbname=template1'; $locator .= ";host=".$self->host if $self->host; } print STDERR "locator:$locator\n" if $ENV{SQL_TRACE}; my $db = DBI->connect( $locator, $self->user, $self->password, {RaiseError => 1} ) or confess "Can't connect to server"; my $db_name = $self->dbname; $db->do("CREATE DATABASE $db_name"); $db->disconnect; push(@{$self->{"_created_dbs"}}, $db_name); $self->do_sql_file(@{$self->schema_sql}); } sub test_locator { my( $self ) = @_; my %dbname_param = ("mysql" => "database=", "Pg" => "dbname=", "Oracle" => ""); my $locator = 'dbi:'. $self->driver .":". $dbname_param{$self->driver()} . $self->dbname; foreach my $meth (qw{ host port }) { if (my $value = $self->$meth()) { $locator .= ";$meth=$value"; } } return $locator; } sub db_handle { my( $self, $no_create ) = @_; unless ($self->{'_db_handle'} || $no_create) { $self->{'_db_handle'} = DBI->connect( $self->test_locator, $self->user, $self->password, {RaiseError => 1} ) or confess "Can't connect to server"; } return $self->{'_db_handle'}; } sub get_DBAdaptor { my( $self, $dbc ) = @_; if(! $dbc) { return $self->get_DBContext()->dbadaptor(); } return Bio::DB::BioDB->new(-database => $self->database, -dbcontext => $dbc, -printerror => $ENV{HARNESS_VERBOSE}, -verbose => $ENV{HARNESS_VERBOSE}, ); } sub get_DBContext { my ($self) = @_; my $dbc = Bio::DB::SimpleDBContext->new("-driver" => $self->driver, "-dbname" => $self->dbname, "-host" => $self->host, "-user" => $self->user, "-pass" => $self->password, "-port" => $self->port); my $dbadp = $self->get_DBAdaptor($dbc); $dbc->dbadaptor($dbadp); return $dbc; } sub do_sql_file { my( $self, @files ) = @_; local *SQL; my $i = 0; my $dbh = $self->db_handle; foreach my $file (@files) { my $sql = ''; open SQL, $file or die "Can't read SQL file '$file' : $!"; while () { s/(#|--).*//; # Remove comments next unless /\S/; # Skip lines which are all space $sql .= $_; $sql .= ' '; } close SQL; #Modified split statement, only semicolumns before end of line, #so we can have them inside a string in the statement foreach my $s (grep /\S/, split /;\n/, $sql) { $self->validate_sql($s); $dbh->do($s); $i++ } } return $i; } sub validate_sql { my ($self, $statement) = @_; if ($statement =~ /insert/i) { $statement =~ s/\n/ /g; #remove newlines die ("INSERT should use explicit column names (-c switch in mysqldump)\n$statement\n") unless ($statement =~ /insert.+into.*\(.+\).+values.*\(.+\)/i); } } sub DESTROY { my( $self, $file ) = @_; my $dbh = $self->db_handle("no_create"); if($dbh) { $dbh->disconnect; $dbh = undef; } while(my $db_name = shift(@{$self->{"_created_dbs"}})) { if(! $dbh) { ### FIXME: not portable between different drivers my $locator = 'dbi:'. $self->driver .':host='. $self->host .';'; if ($self->driver eq "Pg") { # HACK! with DBD::Pg we *must* connect to a db $locator = 'dbi:Pg:dbname=template1'; $locator .= ";host=".$self->host if $self->host; } my $db = DBI->connect($locator, $self->user, $self->password, {RaiseError => 0}) or warn "Can't connect to server ($locator), ". "can't drop database $db_name: $@\n"; } $dbh->do("DROP DATABASE $db_name") if $dbh; } $dbh->disconnect() if $dbh; } 1; __END__ =head1 AUTHOR James Gilbert B jgrg@sanger.ac.uk