ensembl-hive  2.7.0
ProxyDBConnection.pm
Go to the documentation of this file.
1 =head1 LICENSE
2 
3 See the NOTICE file distributed with this work for additional information
4 regarding copyright ownership.
5 
6 Licensed under the Apache License, Version 2.0 (the "License");
7 you may not use this file except in compliance with the License.
8 You may obtain a copy of the License at
9 
10  http://www.apache.org/licenses/LICENSE-2.0
11 
12 Unless required by applicable law or agreed to in writing, software
13 distributed under the License is distributed on an "AS IS" BASIS,
14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 See the License for the specific language governing permissions and
16 limitations under the License.
17 
18 =cut
19 
20 
21 =head1 CONTACT
22 
23  Please email comments or questions to the public Ensembl
24  developers list at <http://lists.ensembl.org/mailman/listinfo/dev>.
25 
26  Questions may also be sent to the Ensembl help desk at
27  <http://www.ensembl.org/Help/Contact>.
28 
29 =cut
30 
31 =head1 NAME
32 
33 Bio::EnsEMBL::DBSQL::ProxyDBConnection - Database connection wrapper allowing
34 for one backing connection to be used for multiple DBs
35 
36 =head1 SYNOPSIS
37 
38  my $dbc = Bio::EnsEMBL::DBSQL::DBConnection->new(-HOST => 'host', -PORT => 3306, -USER => 'user');
39  my $p_h_dbc = Bio::EnsEMBL::DBSQL::ProxyDBConnection->new(-DBC => $dbc, -DBNAME => 'human');
40  my $p_m_dbc = Bio::EnsEMBL::DBSQL::ProxyDBConnection->new(-DBC => $dbc, -DBNAME => 'mouse');
41 
42  # With a 10 minute timeout reconnection in milliseconds
43  my $p_h_rc_dbc = Bio::EnsEMBL::DBSQL::ProxyDBConnection->new(-DBC => $dbc, -DBNAME => 'human', -RECONNECT_INTERVAL => (10*60*1000));
44 
45 =head1 DESCRIPTION
46 
47 This class is used to maintain one active connection to a database whilst it
48 appears to be working against multiple schemas. It does this by checking the
49 currently connected database before performing any query which could require
50 a database change such as prepare.
51 
52 This class is only intended for internal use so please do not use unless
53 you are aware of what it will do and what the consequences of its usage are.
54 
55 =head1 METHODS
56 
57 =cut
58 
59 package Bio::EnsEMBL::DBSQL::ProxyDBConnection;
60 
61 use strict;
62 use warnings;
63 
64 use base qw/Bio::EnsEMBL::Utils::Proxy/;
65 
66 use Bio::EnsEMBL::Utils::Argument qw/rearrange/;
67 use Bio::EnsEMBL::Utils::Exception qw/warning throw/;
69 
70 use Time::HiRes qw/time/;
71 
72 sub new {
73  my ($class, @args) = @_;
74  my ($dbc, $dbname, $reconnect_interval) = rearrange([qw/DBC DBNAME RECONNECT_INTERVAL/], @args);
75  throw "No DBConnection -DBC given" unless $dbc;
76  throw "No database name -DBNAME given" unless $dbname;
77  my $self = $class->SUPER::new($dbc);
78  $self->dbname($dbname);
79  if($reconnect_interval) {
80  $self->reconnect_interval($reconnect_interval);
81  $self->_last_used();
82  }
83  return $self;
84 }
85 
86 =head2 switch_database
87 
88  Description : Performs a switch of the backing DBConnection if the currently
89  connected database is not the same as the database this proxy
90  wants to connect to. It currently supports MySQL, Oracle and
91  Postges switches is untested with all bar MySQL. If it
92  cannot do a live DB/schema switch then it will disconnect
93  the connection and then wait for the next process to
94  connect therefore switching the DB.
95  Exceptions : None but will warn if you attempt to switch a DB with
96  active kids attached to the proxied database handle.
97 
98 =cut
99 
100 sub switch_database {
101  my ($self) = @_;
102  my $proxy = $self->__proxy();
103  my $backing_dbname = $proxy->dbname();
104  my $dbname = $self->dbname();
105 
106  my $switch = 0;
107  if(defined $dbname) {
108  if(defined $backing_dbname) {
109  $switch = ($dbname ne $backing_dbname) ? 1 : 0;
110  }
111  else {
112  $switch = 1;
113  }
114  }
115  else {
116  $switch = 1 if defined $backing_dbname;
117  }
118 
119  if($switch) {
120  $proxy->dbname($dbname);
121  if($proxy->connected()) {
122  my $kids = $proxy->db_handle()->{Kids};
123  my $driver = lc($proxy->driver());
124  #Edit to add other DB switching strategies on a per driver basis
125  if($driver eq 'mysql') {
126  $proxy->do('use '.$dbname);
127  }
128  elsif($driver eq 'oracle') {
129  $proxy->do('ALTER SESSION SET CURRENT_SCHEMA = '.$dbname);
130  }
131  elsif($driver eq 'pg') {
132  $proxy->do('set search_path to '.$dbname);
133  }
134  else {
135  if($kids > 0) {
136  warning "Attempting a database switch from '$backing_dbname' to '$dbname' with $kids active handle(s). Check your logic or do not use a ProxyDBConnection";
137  }
138  $proxy->disconnect_if_idle();
139  }
140  }
141  }
142 
143  return $switch;
144 }
145 
146 =head2 check_reconnection
147 
148  Description : Looks to see if the last time we used the backing DBI
149  connection was greater than the reconnect_interval()
150  provided at construction or runtime. If enought time has
151  elapsed then a reconnection is attempted. We do not
152  attempt a reconnection if:
153 
154  - No reconnect_interval was set
155  - The connection was not active
156 
157  Exceptions : None apart from those raised from the reconnect() method
158  from DBConnection
159 =cut
160 
161 sub check_reconnection {
162  my ($self) = @_;
163  #Return early if we had no reconnection interval
164  return unless $self->{reconnect_interval};
165 
166  my $proxy = $self->__proxy();
167 
168  #Only attempt it if we were connected; otherwise we can just skip
169  if($proxy->connected()) {
170  if($self->_require_reconnect()) {
171  $proxy->reconnect();
172  }
173  $self->_last_used();
174  }
175  return;
176 }
177 
178 # Each time this is called we record the current time in seconds
179 # to be used by the _require_reconnect() method
180 sub _last_used {
181  my ($self) = @_;
182  $self->{_last_used} = int(time()*1000);
183  return;
184 }
185 
186 # Uses the _last_used() time and the current reconnect_interval() to decide
187 # if the connection has been unused for long enough that we should attempt
188 # a reconnect
189 sub _require_reconnect {
190  my ($self) = @_;
191  my $interval = $self->reconnect_interval();
192  return unless $interval;
193  my $last_used = $self->{_last_used};
194  my $time_elapsed = int(time()*1000) - $last_used;
195  return $time_elapsed > $interval ? 1 : 0;
196 }
197 
198 =head2 reconnect_interval
199 
200  Arg[1] : Integer reconnection interval in milliseconds
201  Description : Accessor for the reconnection interval expressed in milliseconds
202  Returntype : Int miliseconds for a reconnection interval
203 
204 =cut
205 
206 sub reconnect_interval {
207  my ($self, $reconnect_interval) = @_;
208  $self->{'reconnect_interval'} = $reconnect_interval if defined $reconnect_interval;
209  return $self->{'reconnect_interval'};
210 }
211 
212 =head2 dbname
213 
214  Arg[1] : String DB name
215  Description : Accessor for the name of the database we should use whenever
216  a DBConnection request is made via this class
217  Returntype : String the name of the database which we should use
218  Exceptions : None
219 
220 =cut
221 
222 sub dbname {
223  my ($self, $dbname) = @_;
224  $self->{'dbname'} = $dbname if defined $dbname;
225  return $self->{'dbname'};
226 }
227 
228 my %SWITCH_METHODS = map { $_ => 1 } qw/
229  connect
230  db_handle
231  do
232  prepare
233  reconnect
234  work_with_db_handle
235 /;
236 
237 
238 # Manual override of the SqlHelper accessor to ensure it always gets the Proxy
239 sub sql_helper {
240  my ($self) = @_;
241  if(! exists $self->{_sql_helper}) {
242  my $helper = Bio::EnsEMBL::Utils::SqlHelper->new(-DB_CONNECTION => $self);
243  $self->{_sql_helper} = $helper;
244  }
245  return $self->{_sql_helper};
246 }
247 
248 sub __resolver {
249  my ($self, $package, $method) = @_;
250  if($self->__proxy()->can($method)) {
251  if($SWITCH_METHODS{$method}) {
252  return sub {
253  my ($local_self, @args) = @_;
254  $local_self->check_reconnection();
255  $local_self->switch_database();
256  $local_self->_last_used();
257  return $local_self->__proxy()->$method(@args);
258  };
259  }
260  else {
261  return sub {
262  my ($local_self, @args) = @_;
263  return $local_self->__proxy()->$method(@args);
264  };
265  }
266  }
267  return;
268 }
269 
270 1;
Bio::EnsEMBL::DBSQL::ProxyDBConnection
Definition: ProxyDBConnection.pm:30
usage
public usage()
map
public map()
Bio::EnsEMBL::DBSQL::ProxyDBConnection::new
public new()
Bio::EnsEMBL::Utils::Proxy
Definition: Proxy.pm:38
Bio::EnsEMBL::Utils::SqlHelper::new
public Instance new()
Bio::EnsEMBL::DBSQL::DBConnection
Definition: DBConnection.pm:42
Bio::EnsEMBL::DBSQL::DBConnection::new
public Bio::EnsEMBL::DBSQL::DBConnection new()
Bio::EnsEMBL::Utils::SqlHelper
Definition: SqlHelper.pm:55
Bio::EnsEMBL::DBSQL::DBConnection::dbname
public String dbname()
Bio::EnsEMBL::Utils::Argument
Definition: Argument.pm:34
Bio::EnsEMBL::DBSQL::DBConnection::reconnect
public void reconnect()
Bio::EnsEMBL::Utils::Exception
Definition: Exception.pm:68