0
|
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;
|