diff variant_effect_predictor/Bio/EnsEMBL/DBSQL/ProxyDBConnection.pm @ 0:1f6dce3d34e0

Uploaded
author mahtabm
date Thu, 11 Apr 2013 02:01:53 -0400
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/variant_effect_predictor/Bio/EnsEMBL/DBSQL/ProxyDBConnection.pm	Thu Apr 11 02:01:53 2013 -0400
@@ -0,0 +1,260 @@
+=head1 LICENSE
+
+  Copyright (c) 1999-2012 The European Bioinformatics Institute and
+  Genome Research Limited.  All rights reserved.
+
+  This software is distributed under a modified Apache license.
+  For license details, please see
+
+    http://www.ensembl.org/info/about/code_licence.html
+
+=head1 CONTACT
+
+  Please email comments or questions to the public Ensembl
+  developers list at <dev@ensembl.org>.
+
+  Questions may also be sent to the Ensembl help desk at
+  <helpdesk@ensembl.org>.
+
+=cut
+
+=head1 NAME
+
+Bio::EnsEMBL::DBSQL::ProxyDBConnection - Database connection wrapper allowing
+for one backing connection to be used for multiple DBs
+
+=head1 SYNOPSIS
+
+  my $dbc = Bio::EnsEMBL::DBSQL::DBConnection->new(-HOST => 'host', -PORT => 3306, -USER => 'user');
+  my $p_h_dbc = Bio::EnsEMBL::DBSQL::ProxyDBConnection->new(-DBC => $dbc, -DBNAME => 'human');
+  my $p_m_dbc = Bio::EnsEMBL::DBSQL::ProxyDBConnection->new(-DBC => $dbc, -DBNAME => 'mouse');
+  
+  # With a 10 minute timeout reconnection in milliseconds
+  my $p_h_rc_dbc = Bio::EnsEMBL::DBSQL::ProxyDBConnection->new(-DBC => $dbc, -DBNAME => 'human', -RECONNECT_INTERVAL => (10*60*1000));
+
+=head1 DESCRIPTION
+
+This class is used to maintain one active connection to a database whilst it
+appears to be working against multiple schemas. It does this by checking the
+currently connected database before performing any query which could require
+a database change such as prepare.
+
+This class is only intended for internal use so please do not use unless
+you are aware of what it will do and what the consequences of its usage are.
+
+=head1 METHODS
+
+=cut
+
+package Bio::EnsEMBL::DBSQL::ProxyDBConnection;
+
+use strict;
+use warnings;
+
+use base qw/Bio::EnsEMBL::Utils::Proxy/;
+
+use Bio::EnsEMBL::Utils::Argument qw/rearrange/;
+use Bio::EnsEMBL::Utils::Exception qw/warning throw/;
+use Bio::EnsEMBL::Utils::SqlHelper;
+
+use Time::HiRes qw/time/;
+
+sub new {
+  my ($class, @args) = @_;
+  my ($dbc, $dbname, $reconnect_interval) = rearrange([qw/DBC DBNAME RECONNECT_INTERVAL/], @args);
+  throw "No DBConnection -DBC given" unless $dbc;
+  throw "No database name -DBNAME given" unless $dbname;
+  my $self = $class->SUPER::new($dbc);
+  $self->dbname($dbname);
+  if($reconnect_interval) {
+    $self->reconnect_interval($reconnect_interval);
+    $self->_last_used();
+  }
+  return $self;  
+}
+
+=head2 switch_database
+
+  Description : Performs a switch of the backing DBConnection if the currently
+                connected database is not the same as the database this proxy
+                wants to connect to. It currently supports MySQL, Oracle and
+                Postges switches is untested with all bar MySQL. If it
+                cannot do a live DB/schema switch then it will disconnect
+                the connection and then wait for the next process to
+                connect therefore switching the DB.
+  Exceptions  : None but will warn if you attempt to switch a DB with 
+                active kids attached to the proxied database handle.
+
+=cut
+
+sub switch_database {
+  my ($self) = @_;
+  my $proxy = $self->__proxy();
+  my $backing_dbname = $proxy->dbname();
+  my $dbname = $self->dbname();
+  
+  my $switch = 0;
+  if(defined $dbname) {
+    if(defined $backing_dbname) {
+      $switch = ($dbname ne $backing_dbname) ? 1 : 0;
+    }
+    else {
+      $switch = 1;
+    }
+  }
+  else {
+    $switch = 1 if defined $backing_dbname;
+  }
+  
+  if($switch) {
+    $proxy->dbname($dbname);
+    if($proxy->connected()) {
+      my $kids = $proxy->db_handle()->{Kids};
+      my $driver = lc($proxy->driver());
+      #Edit to add other DB switching strategies on a per driver basis
+      if($driver eq 'mysql') {
+        $proxy->do('use '.$dbname);
+      }
+      elsif($driver eq 'oracle') {
+        $proxy->do('ALTER SESSION SET CURRENT_SCHEMA = '.$dbname);
+      }
+      elsif($driver eq 'pg') {
+        $proxy->do('set search_path to '.$dbname);
+      }
+      else {
+        if($kids > 0) {
+          warning "Attempting a database switch from '$backing_dbname' to '$dbname' with $kids active handle(s). Check your logic or do not use a ProxyDBConnection";
+        }
+        $proxy->disconnect_if_idle();
+      }
+    }
+  }
+  
+  return $switch;
+}
+
+=head2 check_reconnection
+
+  Description : Looks to see if the last time we used the backing DBI
+                connection was greater than the reconnect_interval()
+                provided at construction or runtime. If enought time has 
+                elapsed then a reconnection is attempted. We do not
+                attempt a reconnection if:
+                
+                  - No reconnect_interval was set
+                  - The connection was not active
+                
+  Exceptions  : None apart from those raised from the reconnect() method
+                from DBConnection
+=cut
+
+sub check_reconnection {
+  my ($self) = @_;
+  #Return early if we had no reconnection interval
+  return unless $self->{reconnect_interval};
+  
+  my $proxy = $self->__proxy();
+  
+  #Only attempt it if we were connected; otherwise we can just skip
+  if($proxy->connected()) {
+    if($self->_require_reconnect()) {   
+      $proxy->reconnect();
+    }
+    $self->_last_used();
+  }
+  return;
+}
+
+# Each time this is called we record the current time in seconds
+# to be used by the _require_reconnect() method
+sub _last_used {
+  my ($self) = @_;
+  $self->{_last_used} = int(time()*1000);
+  return;
+}
+
+# Uses the _last_used() time and the current reconnect_interval() to decide
+# if the connection has been unused for long enough that we should attempt
+# a reconnect
+sub _require_reconnect {
+  my ($self) = @_;
+  my $interval = $self->reconnect_interval();
+  return unless $interval;
+  my $last_used = $self->{_last_used};
+  my $time_elapsed = int(time()*1000) - $last_used;
+  return $time_elapsed > $interval ? 1 : 0;
+}
+
+=head2 reconnect_interval
+
+	Arg[1]      : Integer reconnection interval in milliseconds
+  Description : Accessor for the reconnection interval expressed in milliseconds
+  Returntype  : Int miliseconds for a reconnection interval
+
+=cut
+
+sub reconnect_interval {
+  my ($self, $reconnect_interval) = @_;
+  $self->{'reconnect_interval'} = $reconnect_interval if defined $reconnect_interval;
+  return $self->{'reconnect_interval'};
+}
+
+=head2 dbname
+
+	Arg[1]      : String DB name
+  Description : Accessor for the name of the database we should use whenever
+                a DBConnection request is made via this class
+  Returntype  : String the name of the database which we should use
+  Exceptions  : None
+
+=cut
+
+sub dbname {
+  my ($self, $dbname) = @_;
+  $self->{'dbname'} = $dbname if defined $dbname;
+  return $self->{'dbname'};
+}
+
+my %SWITCH_METHODS = map { $_ => 1 } qw/
+  connect
+  db_handle
+  do
+  prepare
+  reconnect
+  work_with_db_handle
+/;
+
+
+# Manual override of the SqlHelper accessor to ensure it always gets the Proxy
+sub sql_helper {
+  my ($self) = @_;
+  if(! exists $self->{_sql_helper}) {
+    my $helper = Bio::EnsEMBL::Utils::SqlHelper->new(-DB_CONNECTION => $self);
+    $self->{_sql_helper} = $helper;
+  }
+  return $self->{_sql_helper};
+}
+
+sub __resolver {
+  my ($self, $package, $method) = @_;
+  if($self->__proxy()->can($method)) {
+    if($SWITCH_METHODS{$method}) {
+      return sub {
+        my ($local_self, @args) = @_;
+        $local_self->check_reconnection();
+        $local_self->switch_database();
+        $local_self->_last_used();
+        return $local_self->__proxy()->$method(@args);
+      };
+    }
+    else {
+      return sub {
+        my ($local_self, @args) = @_;
+        return $local_self->__proxy()->$method(@args);
+      };
+    }
+  }
+  return;
+}
+
+1;