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

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