DBConnection.pm 32 KB
Newer Older
1
=head1 LICENSE
2

3
  Copyright (c) 1999-2013 The European Bioinformatics Institute and
4
  Genome Research Limited.  All rights reserved.
5

6 7
  This software is distributed under a modified Apache license.
  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 17
  Questions may also be sent to the Ensembl help desk at
  <helpdesk@ensembl.org>.
18

19
=cut
20

21
=head1 NAME
22

23
Bio::EnsEMBL::DBSQL::DBConnection
24

25
=head1 SYNOPSIS
26

27 28 29 30 31 32
  $dbc = Bio::EnsEMBL::DBSQL::DBConnection->new(
    -user   => 'anonymous',
    -dbname => 'homo_sapiens_core_20_34c',
    -host   => 'ensembldb.ensembl.org',
    -driver => 'mysql',
  );
33

34 35
  # SQL statements should be created/executed through this modules
  # prepare() and do() methods.
36

37 38 39 40 41 42 43 44 45
  $sth = $dbc->prepare("SELECT something FROM yourtable");

  $sth->execute();

  # do something with rows returned ...

  $sth->finish();

=head1 DESCRIPTION
46

47 48 49
This class is a wrapper around DBIs datbase handle.  It provides some
additional functionality such as the ability to automatically disconnect
when inactive and reconnect when needed.
50

51 52
Generally this class will be used through one of the object adaptors or
the Bio::EnsEMBL::Registry and will not be instantiated directly.
53

54
=head1 METHODS
55 56 57 58 59 60 61 62 63 64 65 66

=cut


package Bio::EnsEMBL::DBSQL::DBConnection;

use vars qw(@ISA);
use strict;

use Bio::EnsEMBL::Root;
use DBI;

67 68
use Bio::EnsEMBL::DBSQL::StatementHandle;

69 70 71 72
use Bio::EnsEMBL::Utils::Exception qw/deprecate throw info warning/;
use Bio::EnsEMBL::Utils::Argument qw/rearrange/;
use Bio::EnsEMBL::Utils::Scalar qw/assert_ref wrap_array/;
use Bio::EnsEMBL::Utils::SqlHelper;
73

74
@ISA = qw(Bio::EnsEMBL::Root); # for backwards compatibility
75

Graham McVicker's avatar
Graham McVicker committed
76 77
=head2 new

78
  Arg [DBNAME] : (optional) string
Graham McVicker's avatar
Graham McVicker committed
79 80 81 82 83 84 85 86
                 The name of the database to connect to.
  Arg [HOST] : (optional) string
               The domain name of the database host to connect to.  
               'localhost' by default. 
  Arg [USER] : string
               The name of the database user to connect with 
  Arg [PASS] : (optional) string
               The password to be used to connect to the database
87
  Arg [PORT] : (optional) int
Graham McVicker's avatar
Graham McVicker committed
88
               The port to use when connecting to the database
89
               3306 by default if the driver is mysql.
Graham McVicker's avatar
Graham McVicker committed
90 91 92
  Arg [DRIVER] : (optional) string
                 The type of database driver to use to connect to the DB
                 mysql by default.
93 94 95 96 97 98 99 100 101 102
  Arg [DBCONN] : (optional)
                 Open another handle to the same database as another connection
                 If this argument is specified, no other arguments should be
                 specified.
  Arg [DISCONNECT_WHEN_INACTIVE]: (optional) boolean
                 If set to true, the database connection will be disconnected
                 everytime there are no active statement handles. This is
                 useful when running a lot of jobs on a compute farm
                 which would otherwise keep open a lot of connections to the
                 database.  Database connections are automatically reopened
103
                 when required.Do not use this option together with RECONNECT_WHEN_CONNECTION_LOST.
104 105 106 107 108 109
  Arg [WAIT_TIMEOUT]: (optional) integer
                 Time in seconds for the wait timeout to happen. Time after which
                 the connection is deleted if not used. By default this is 28800 (8 hours)
                 on most systems. 
                 So set this to greater than this if your connection are getting deleted.
                 Only set this if you are having problems and know what you are doing.
110 111 112 113 114 115 116
  Arg [RECONNECT_WHEN_CONNECTION_LOST]: (optional) boolean
                 In case you're reusing the same database connection, i.e. DISCONNECT_WHEN_INACTIVE is 
                 set to false and running a job which takes a long time to process (over 8hrs), 
                 which means that the db connection may be lost, set this option to true. 
                 On each prepare or do statement the db handle will be pinged and the database 
                 connection will be reconnected if it's lost.
                
117
  Example    : $dbc = Bio::EnsEMBL::DBSQL::DBConnection->new
118 119 120
                  (-user   => 'anonymous',
                   -dbname => 'homo_sapiens_core_20_34c',
                   -host   => 'ensembldb.ensembl.org',
Ian Longden's avatar
Ian Longden committed
121
                   -driver => 'mysql');
122

123
  Description: Constructor for a Database Connection. Any adaptors that require
Graham McVicker's avatar
Graham McVicker committed
124
               database connectivity should inherit from this class.
125
  Returntype : Bio::EnsEMBL::DBSQL::DBConnection
Graham McVicker's avatar
Graham McVicker committed
126 127
  Exceptions : thrown if USER or DBNAME are not specified, or if the database
               cannot be connected to.
128
  Caller     : Bio::EnsEMBL::Utils::ConfigRegistry ( for newer code using the registry)
Ian Longden's avatar
Ian Longden committed
129
               Bio::EnsEMBL::DBSQL::DBAdaptor        ( for old style code)
130
  Status     : Stable
Graham McVicker's avatar
Graham McVicker committed
131 132 133

=cut

134 135 136
sub new {
  my $class = shift;

137 138 139
  my (
    $db,                  $host,     $driver,
    $user,                $password, $port,
140
    $inactive_disconnect, $dbconn,   $wait_timeout, $reconnect
141 142 143
    )
    = rearrange( [
      'DBNAME', 'HOST', 'DRIVER', 'USER', 'PASS', 'PORT',
144
      'DISCONNECT_WHEN_INACTIVE', 'DBCONN', 'WAIT_TIMEOUT', 'RECONNECT_WHEN_CONNECTION_LOST'
145 146 147
    ],
    @_
    );
148

149 150 151
  my $self = {};
  bless $self, $class;

152
  if($dbconn) {
153
    if($db || $host || $driver || $password || $port || $inactive_disconnect || $reconnect) {
154
      throw("Cannot specify other arguments when -DBCONN argument used.");
155 156
    }

157
    $self->driver($dbconn->driver());
158 159
    $self->host($dbconn->host());
    $self->port($dbconn->port());
160 161 162
    $self->username($dbconn->username());
    $self->password($dbconn->password());
    $self->dbname($dbconn->dbname());
163

164
    if($dbconn->disconnect_when_inactive()) {
Graham McVicker's avatar
Graham McVicker committed
165
      $self->disconnect_when_inactive(1);
166 167 168
    }
  } else {
    $driver ||= 'mysql';
169
    
170 171 172 173 174 175 176 177 178 179 180 181 182
    if($driver eq 'mysql') {
        $user || throw("-USER argument is required.");
        $host ||= 'mysql';
        if(!defined($port)){
            $port   = 3306;
            if($host eq "ensembldb.ensembl.org"){
                if( $db =~ /\w+_\w+_\w+_(\d+)/){
                    if($1 >= 48){
                        $port = 5306;
                    }
                }
            }
        }
183 184 185 186
    } elsif($driver eq 'pgsql') {
        if(!defined($port)){
            $port   = 5432;
        }
187 188
    }

189
    $wait_timeout   ||= 0;
190

191
    $self->driver($driver);
192 193
    $self->host( $host );
    $self->port($port);
194 195 196
    $self->username( $user );
    $self->password( $password );
    $self->dbname( $db );
197
    $self->timeout($wait_timeout);
198

199 200 201
    if($inactive_disconnect) {
      $self->disconnect_when_inactive($inactive_disconnect);
    }
202
    if($reconnect) {
203
      $self->reconnect_when_lost($reconnect);
204
    }
205
  }
206

Ian Longden's avatar
Ian Longden committed
207 208 209
#  if(defined $dnadb) {
#    $self->dnadb($dnadb);
#  }
Ian Longden's avatar
Ian Longden committed
210
  return $self;
211 212 213
}


214 215
=head2 connect

216 217 218
  Example    : $dbcon->connect()
  Description: Connects to the database using the connection attribute 
               information.
219 220 221
  Returntype : none
  Exceptions : none
  Caller     : new, db_handle
222
  Status     : Stable
223 224 225 226

=cut

sub connect {
227 228 229
  my ($self) = @_;

  if ( $self->connected() ) { return }
230

231
  $self->connected(1);
232

233
  if ( defined( $self->db_handle() ) and $self->db_handle()->ping() ) {
234 235
    warning(   "unconnected db_handle is still pingable, "
             . "reseting connected boolean\n" );
236 237
  }

238
  my ( $dsn, $dbh );
239
  my $dbname = $self->dbname();
240

241
  if ( $self->driver() eq "Oracle" ) {
242 243 244 245

    $dsn = "DBI:Oracle:";

    eval {
246 247 248 249 250
      $dbh = DBI->connect( $dsn,
                           sprintf( "%s@%s",
                                    $self->username(), $dbname ),
                           $self->password(),
                           { 'RaiseError' => 1, 'PrintError' => 0 } );
251
    };
252

253
  } elsif ( $self->driver() eq "ODBC" ) {
254 255 256 257

    $dsn = sprintf( "DBI:ODBC:%s", $self->dbname() );

    eval {
258 259 260 261 262 263 264 265
      $dbh = DBI->connect( $dsn,
                           $self->username(),
                           $self->password(), {
                             'LongTruncOk'     => 1,
                             'LongReadLen'     => 2**16 - 8,
                             'RaiseError'      => 1,
                             'PrintError'      => 0,
                             'odbc_cursortype' => 2 } );
266
    };
267

268
  } elsif ( $self->driver() eq "Sybase" ) {
269 270 271 272
    my $dbparam = ($dbname) ? ";database=${dbname}" : q{};

    $dsn = sprintf( "DBI:Sybase:server=%s%s;tdsLevel=CS_TDS_495",
                    $self->host(), $dbparam );
273 274

    eval {
275 276 277 278 279 280
      $dbh = DBI->connect( $dsn,
                           $self->username(),
                           $self->password(), {
                             'LongTruncOk' => 1,
                             'RaiseError'  => 1,
                             'PrintError'  => 0 } );
281
    };
282

283 284 285 286 287
  } elsif ( lc( $self->driver() ) eq 'sqlite' ) {

    throw "We require a dbname to connect to a SQLite database"
      if !$dbname;

288
    $dsn = sprintf( "DBI:SQLite:%s", $dbname );
289 290

    eval {
291
      $dbh = DBI->connect( $dsn, '', '', { 'RaiseError' => 1, } );
292 293
    };

294
  } else {
295

296
    my $dbparam = ($dbname) ? "database=${dbname};" : q{};
297

298 299 300
    my $driver = $self->driver();
    $driver = 'Pg' if($driver eq 'pgsql');

301
    $dsn = sprintf( "DBI:%s:%shost=%s;port=%s",
302
                    $driver, $dbparam,
303 304 305
                    $self->host(),   $self->port() );

    if ( $self->{'disconnect_when_inactive'} ) {
306
      $self->{'count'}++;
307
      if ( $self->{'count'} > 1000 ) {
308 309
        sleep 1;
        $self->{'count'} = 0;
310 311
      }
    }
312
    eval {
313
      $dbh = DBI->connect( $dsn, $self->username(), $self->password(),
314
                           { 'RaiseError' => 1 } );
315
    };
316
  }
317 318

  if ( !$dbh || $@ || !$dbh->ping() ) {
319 320 321 322 323 324
    warn(   "Could not connect to database "
          . $self->dbname()
          . " as user "
          . $self->username()
          . " using [$dsn] as a locator:\n"
          . $DBI::errstr );
325

326
    $self->connected(0);
327

328 329 330 331 332 333
    throw(   "Could not connect to database "
           . $self->dbname()
           . " as user "
           . $self->username()
           . " using [$dsn] as a locator:\n"
           . $DBI::errstr );
334
  }
335

336
  $self->db_handle($dbh);
337 338 339

  if ( $self->timeout() ) {
    $dbh->do( "SET SESSION wait_timeout=" . $self->timeout() );
340
  }
341

342
  #print("CONNECT\n");
343
} ## end sub connect
344

345 346

=head2 connected
347

348 349 350 351 352 353
  Example    : $dbcon->connected()
  Description: Boolean which tells if DBConnection is connected or not.
               State is set internally, and external processes should not alter state.
  Returntype : undef or 1
  Exceptions : none
  Caller     : db_handle, connect, disconnect_if_idle, user processes
354 355
  Status     : Stable

356 357 358 359
=cut

sub connected {
  my $self = shift;
360 361 362

  # Use the process id ($$) as part of the key for the connected flag.
  # This forces the opening of another connection in a forked subprocess.
363 364 365 366 367 368 369 370 371
  $self->{'connected'.$$} = shift if(@_);
  return $self->{'connected'.$$};
}

sub disconnect_count {
  my $self = shift;
  return $self->{'disconnect_count'} = shift if(@_);
  $self->{'disconnect_count'}=0 unless(defined($self->{'disconnect_count'}));
  return $self->{'disconnect_count'};
372 373
}

374 375 376 377 378 379 380 381 382 383
sub timeout{
  my($self, $arg ) = @_;

  (defined $arg) &&
    ($self->{_timeout} = $arg );

  return $self->{_timeout};

}

384 385 386 387 388 389 390
sub query_count {
  my $self = shift;
  return $self->{'_query_count'} = shift if(@_);
  $self->{'_query_count'}=0 unless(defined($self->{'_query_count'}));
  return $self->{'_query_count'};
}

391 392
=head2 equals

393 394 395 396 397 398
  Example    : warn 'Same!' if($dbc->equals($other_dbc));
  Description: Equality checker for DBConnection objects
  Returntype : boolean
  Exceptions : none
  Caller     : new
  Status     : Stable
399 400 401 402

=cut

  
403 404
sub equals {
  my ( $self, $dbc ) = @_;
405 406 407 408 409 410 411 412 413 414 415 416 417 418
  return 0 if ! defined $dbc;
  my $return = 0;
  my $undef_str = q{!-undef-!};
  my $undef_num = -1;

  $return = 1 if  ( 
    (($self->host() || $undef_str)      eq ($dbc->host() || $undef_str)) &&
    (($self->dbname() || $undef_str)    eq ($dbc->dbname() || $undef_str)) &&
    (($self->port() || $undef_num)      == ($dbc->port() || $undef_num)) &&
    (($self->username() || $undef_str)  eq ($dbc->username() || $undef_str)) &&
    ($self->driver() eq $dbc->driver())
  );
  
  return $return;
419
}
420

Graham McVicker's avatar
Graham McVicker committed
421 422 423 424 425 426 427 428 429 430 431
=head2 driver

  Arg [1]    : (optional) string $arg
               the name of the driver to use to connect to the database
  Example    : $driver = $db_connection->driver()
  Description: Getter / Setter for the driver this connection uses.
               Right now there is no point to setting this value after a
               connection has already been established in the constructor.
  Returntype : string
  Exceptions : none
  Caller     : new
432
  Status     : Stable
Graham McVicker's avatar
Graham McVicker committed
433 434 435

=cut

436 437
sub driver {
  my($self, $arg ) = @_;
438

439 440 441 442 443
  (defined $arg) &&
    ($self->{_driver} = $arg );
  return $self->{_driver};
}

Graham McVicker's avatar
Graham McVicker committed
444 445 446 447 448 449 450 451 452 453 454 455 456

=head2 port

  Arg [1]    : (optional) int $arg
               the TCP or UDP port to use to connect to the database
  Example    : $port = $db_connection->port();
  Description: Getter / Setter for the port this connection uses to communicate
               to the database daemon.  There currently is no point in 
               setting this value after the connection has already been 
               established by the constructor.
  Returntype : string
  Exceptions : none
  Caller     : new
457
  Status     : Stable
Graham McVicker's avatar
Graham McVicker committed
458 459 460

=cut

461
sub port {
462 463 464 465 466
  my ( $self, $value ) = @_;

  if ( defined($value) ) {
    $self->{'_port'} = $value;
  }
467

468
  return $self->{'_port'};
469 470
}

Graham McVicker's avatar
Graham McVicker committed
471 472 473 474 475 476 477 478 479 480 481 482 483

=head2 dbname

  Arg [1]    : (optional) string $arg
               The new value of the database name used by this connection. 
  Example    : $dbname = $db_connection->dbname()
  Description: Getter/Setter for the name of the database used by this 
               connection.  There is currently no point in setting this value
               after the connection has already been established by the 
               constructor.
  Returntype : string
  Exceptions : none
  Caller     : new
484
  Status     : Stable
Graham McVicker's avatar
Graham McVicker committed
485 486 487

=cut

488 489 490 491 492 493 494
sub dbname {
  my ($self, $arg ) = @_;
  ( defined $arg ) &&
    ( $self->{_dbname} = $arg );
  $self->{_dbname};
}

Graham McVicker's avatar
Graham McVicker committed
495 496 497 498 499 500 501 502 503 504 505 506 507

=head2 username

  Arg [1]    : (optional) string $arg
               The new value of the username used by this connection. 
  Example    : $username = $db_connection->username()
  Description: Getter/Setter for the username used by this 
               connection.  There is currently no point in setting this value
               after the connection has already been established by the 
               constructor.
  Returntype : string
  Exceptions : none
  Caller     : new
508
  Status     : Stable
Graham McVicker's avatar
Graham McVicker committed
509 510 511

=cut

512 513 514 515 516
sub username {
  my ($self, $arg ) = @_;
  ( defined $arg ) &&
    ( $self->{_username} = $arg );
  $self->{_username};
517 518
}

Graham McVicker's avatar
Graham McVicker committed
519

Graham McVicker's avatar
Graham McVicker committed
520
=head2 host
Graham McVicker's avatar
Graham McVicker committed
521 522 523 524 525 526 527 528 529 530 531

  Arg [1]    : (optional) string $arg
               The new value of the host used by this connection. 
  Example    : $host = $db_connection->host()
  Description: Getter/Setter for the domain name of the database host use by 
               this connection.  There is currently no point in setting 
               this value after the connection has already been established 
               by the constructor.
  Returntype : string
  Exceptions : none
  Caller     : new
532
  Status     : Stable
Graham McVicker's avatar
Graham McVicker committed
533 534 535

=cut

536
sub host {
537 538 539 540
  my ($self, $arg ) = @_;
  ( defined $arg ) &&
    ( $self->{_host} = $arg );
  $self->{_host};
541 542
}

Graham McVicker's avatar
Graham McVicker committed
543

Graham McVicker's avatar
Graham McVicker committed
544
=head2 password
Graham McVicker's avatar
Graham McVicker committed
545 546

  Arg [1]    : (optional) string $arg
547
               The new value of the password used by this connection.
Graham McVicker's avatar
Graham McVicker committed
548
  Example    : $host = $db_connection->password()
549 550 551 552
  Description: Getter/Setter for the password of to use for this
               connection.  There is currently no point in setting
               this value after the connection has already been
               established by the constructor.
Graham McVicker's avatar
Graham McVicker committed
553 554 555
  Returntype : string
  Exceptions : none
  Caller     : new
556
  Status     : Stable
Graham McVicker's avatar
Graham McVicker committed
557 558 559

=cut

560
sub password {
561 562 563 564 565 566 567 568 569 570 571 572
  my ( $self, $arg ) = @_;

  if ( defined($arg) ) {
    # Use an anonymous subroutine that will return the password when
    # invoked.  This will prevent the password from being accidentally
    # displayed when using e.g. Data::Dumper on a structure containing
    # one of these objects.

    $self->{_password} = sub { $arg };
  }

  return ( ref( $self->{_password} ) && &{ $self->{_password} } ) || '';
573 574
}

Ian Longden's avatar
Ian Longden committed
575

576

577
=head2 disconnect_when_inactive
578

579 580 581 582 583 584 585 586 587 588
  Arg [1]    : (optional) boolean $newval
  Example    : $db->disconnect_when_inactive(1);
  Description: Getter/Setter for the disconnect_when_inactive flag.  If set
               to true this DBConnection will continually disconnect itself
               when there are no active statement handles and reconnect as
               necessary.  Useful for farm environments when there can be
               many (often inactive) open connections to a database at once.
  Returntype : boolean
  Exceptions : none
  Caller     : Pipeline
589
  Status     : Stable
590 591 592

=cut

593
sub disconnect_when_inactive {
594
  my ( $self, $value ) = @_;
595

596 597 598
  if ( defined($value) ) {
    $self->{'disconnect_when_inactive'} = $value;
    if ($value) {
599
      $self->disconnect_if_idle();
Ian Longden's avatar
Ian Longden committed
600
    }
601
  }
602

603
  return $self->{'disconnect_when_inactive'};
604 605
}

606

607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633
=head2 reconnect_when_lost

  Arg [1]    : (optional) boolean $newval
  Example    : $db->reconnect_when_lost(1);
  Description: Getter/Setter for the reconnect_when_lost flag.  If set
               to true the db handle will be pinged on each prepare or do statement 
               and the connection will be reestablished in case it's lost.
               Useful for long running jobs (over 8hrs), which means that the db 
               connection may be lost.
  Returntype : boolean
  Exceptions : none
  Caller     : Pipeline
  Status     : Stable

=cut

sub reconnect_when_lost {
  my ( $self, $value ) = @_;

  if ( defined($value) ) {
    $self->{'reconnect_when_lost'} = $value;
  }

  return $self->{'reconnect_when_lost'};
}


634

635 636 637 638 639 640 641 642 643
=head2 locator

  Arg [1]    : none
  Example    : $locator = $dbc->locator;
  Description: Constructs a locator string for this database connection
               that can, for example, be used by the DBLoader module
  Returntype : string
  Exceptions : none
  Caller     : general
644
  Status     : Stable
645 646 647 648 649

=cut


sub locator {
650
  my ($self) = @_;
651

652 653 654 655
  return sprintf(
    "%s/host=%s;port=%s;dbname=%s;user=%s;pass=%s",
    ref($self),      $self->host(),     $self->port(),
    $self->dbname(), $self->username(), $self->password() );
656 657 658
}


659 660 661 662 663 664 665 666 667
=head2 db_handle

  Arg [1]    : DBI Database Handle $value
  Example    : $dbh = $db_connection->db_handle() 
  Description: Getter / Setter for the Database handle used by this
               database connection.
  Returntype : DBI Database Handle
  Exceptions : none
  Caller     : new, DESTROY
668
  Status     : Stable
669 670 671 672 673 674

=cut

sub db_handle {
   my $self = shift;

675 676 677 678
   # Use the process id ($$) as part of the key for the database handle
   # this makes this object fork safe.  fork() does not makes copies
   # of the open socket which creates problems when one of the forked
   # processes disconnects,
679 680
   return $self->{'db_handle'.$$} = shift if(@_);
   return $self->{'db_handle'.$$} if($self->connected);
681

682
   $self->connect();
683
   return $self->{'db_handle'.$$};
684 685 686 687 688 689 690 691 692 693 694 695 696 697
}


=head2 prepare

  Arg [1]    : string $string
               the SQL statement to prepare
  Example    : $sth = $db_connection->prepare("SELECT column FROM table");
  Description: Prepares a SQL statement using the internal DBI database handle
               and returns the DBI statement handle.
  Returntype : DBI statement handle
  Exceptions : thrown if the SQL statement is empty, or if the internal
               database handle is not present
  Caller     : Adaptor modules
698
  Status     : Stable
699 700 701 702

=cut

sub prepare {
703
   my ($self,@args) = @_;
704

705
   if( ! $args[0] ) {
706 707 708
     throw("Attempting to prepare an empty SQL query.");
   }

709
   #warn "SQL(".$self->dbname."):" . join(' ', @args) . "\n";
710 711 712
   if ( ($self->reconnect_when_lost()) and (!$self->db_handle()->ping()) ) { 
       $self->reconnect();
   }
713
   my $sth = $self->db_handle->prepare(@args);
714 715 716 717 718

   # return an overridden statement handle that provides us with
   # the means to disconnect inactive statement handles automatically
   bless $sth, "Bio::EnsEMBL::DBSQL::StatementHandle";
   $sth->dbc($self);
719
   $sth->sql($args[0]);
720

721
   $self->query_count($self->query_count()+1);
722 723 724
   return $sth;
}

725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741
=head2 reconnect

  Example    : $dbcon->reconnect()
  Description: Reconnects to the database using the connection attribute 
               information if db_handle no longer pingable.
  Returntype : none
  Exceptions : none
  Caller     : new, db_handle
  Status     : Stable

=cut

sub reconnect {
  my ($self) = @_;
  $self->connected(undef);
  $self->db_handle(undef);
  $self->connect();
742
  return;
743 744
}

745 746 747 748 749 750 751 752 753 754 755

=head2 do

  Arg [1]    : string $string
               the SQL statement to prepare
  Example    : $sth = $db_connection->do("SELECT column FROM table");
  Description: Executes a SQL statement using the internal DBI database handle.
  Returntype : Result of DBI dbh do() method
  Exceptions : thrown if the SQL statement is empty, or if the internal
               database handle is not present.
  Caller     : Adaptor modules
756
  Status     : Stable
757 758 759 760 761 762 763 764 765 766

=cut

sub do {
   my ($self,$string) = @_;

   if( ! $string ) {
     throw("Attempting to do an empty SQL query.");
   }

767
   # warn "SQL(".$self->dbname."): $string";
768 769 770 771 772 773 774 775 776 777 778 779 780
   my $error;
   
   my $do_result = $self->work_with_db_handle(sub {
     my ($dbh) = @_;
     my $result = eval { $dbh->do($string) };
     $error = $@ if $@;
     return $result;
   });
   
   throw "Detected an error whilst executing statement '$string': $error" if $error;
 
   return $do_result;
}
781

782
=head2 work_with_db_handle
783

784 785 786 787 788 789 790 791 792
  Arg [1]    : CodeRef $callback
  Example    : my $q_t = $dbc->work_with_db_handle(sub { my ($dbh) = @_; return $dbh->quote_identifier('table'); });
  Description: Gives access to the DBI handle to execute methods not normally
               provided by the DBConnection interface
  Returntype : Any from callback
  Exceptions : If the callback paramater is not a CodeRef; all other 
               errors are re-thrown after cleanup.
  Caller     : Adaptor modules
  Status     : Stable
793

794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815
=cut

sub work_with_db_handle {
  my ($self, $callback) = @_;
  my $wantarray = wantarray;
  assert_ref($callback, 'CODE', 'callback');
  if( $self->reconnect_when_lost() && !$self->db_handle()->ping()) { 
    $self->reconnect();
  }
  my @results;
  eval {
    if($wantarray) { 
      @results = $callback->($self->db_handle())
    }
    elsif(defined $wantarray) {
      $results[0] = $callback->($self->db_handle());
    }
    else {
      $callback->($self->db_handle());
    }
  };
  my $original_error = $@;
816
  
817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833
  $self->query_count($self->query_count()+1);
  eval {
    if($self->disconnect_when_inactive()) {
      $self->disconnect_if_idle();
    }
  };
  if($@) {
    warning "Detected an error whilst attempting to disconnect the DBI handle: $@";
  }
  if($original_error) {
    throw "Detected an error when running DBI wrapper callback:\n$original_error";
  }
  
  if(defined $wantarray) {
    return ($wantarray) ? @results : $results[0];
  }
  return;
834 835
}

836
=head2 prevent_disconnect
837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854

  Arg[1]      : CodeRef $callback
  Example     : $dbc->prevent_disconnect(sub { $dbc->do('do something'); $dbc->do('something else')});
  Description : A wrapper method which prevents database disconnection for the
                duration of the callback. This is very useful if you need
                to make multiple database calls avoiding excessive database
                connection creation/destruction but still want the API
                to disconnect after the body of work. 
                
                The value of C<disconnect_when_inactive()> is set to 0 no
                matter what the original value was & after $callback has
                been executed. If C<disconnect_when_inactive()> was 
                already set to 0 then this method will be an effective no-op.
  Returntype  : None
  Exceptions  : Raised if there are issues with reverting the connection to its
                default state.
  Caller      : DBConnection methods
  Status      : Beta
855

856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902
=cut

sub prevent_disconnect {
  my ($self, $callback) = @_;
  assert_ref($callback, 'CODE', 'callback');
  my $original_dwi = $self->disconnect_when_inactive();
  $self->disconnect_when_inactive(0);
  eval { $callback->(); };
  my $original_error = $@;
  eval {
    $self->disconnect_when_inactive($original_dwi);    
  };
  if($@) {
    warning "Detected an error whilst attempting to reset disconnect_when_idle: $@";
  }
  if($original_error) {
    throw "Detected an error when running DBI wrapper callback:\n$original_error";
  }
  return;
}

=head2 quote_identifier

  Arg [n]    : scalar/ArrayRef
  Example    : $q = $dbc->quote_identifier('table', 'other');
               $q = $dbc->quote_identifier([undef, 'my_db', 'table'], [undef, 'my_db', 'other']);
  Description: Executes the DBI C<quote_identifier> method which will quote
               any given string using the database driver's quote character.
  Returntype : ArrayRef
  Exceptions : None
  Caller     : General
  Status     : Stable

=cut

sub quote_identifier {
  my ($self, @identifiers) = @_;
  return $self->work_with_db_handle(sub {
    my ($dbh) = @_;
    my @output;
    foreach my $identifier_array (@identifiers) {
      $identifier_array = wrap_array($identifier_array);
      push(@output, $dbh->quote_identifier(@{$identifier_array}));
    }
    return \@output;
  });
}
903 904 905 906 907 908

=head2 disconnect_if_idle

  Arg [1]    : none
  Example    : $dbc->disconnect_if_idle();
  Description: Disconnects from the database if there are no currently active
909
               statement handles. 
910 911 912
               It is called automatically by the DESTROY method of the
               Bio::EnsEMBL::DBSQL::SQL::StatementHandle if the
               disconect_when_inactive flag is set.
913 914
               Users may call it whenever they want to disconnect. Connection will
               reestablish on next access to db_handle()
Kieron Taylor's avatar
Kieron Taylor committed
915
  Returntype : 1 or 0
916
               1=problem trying to disconnect while a statement handle was still active
917 918 919
  Exceptions : none
  Caller     : Bio::EnsEMBL::DBSQL::SQL::StatementHandle::DESTROY
               Bio::EnsEMBL::DBSQL::DBConnection::do
920
  Status     : Stable
921 922 923 924 925 926

=cut

sub disconnect_if_idle {
  my $self = shift;

927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945
  return 0 if(!$self->connected());
  my $db_handle = $self->db_handle();
  return 0 unless(defined($db_handle));

  #printf("disconnect_if_idle : kids=%d activekids=%d\n",
  #       $db_handle->{Kids}, $db_handle->{ActiveKids});

  #If InactiveDestroy is set, don't disconnect.
  #To comply with DBI specification
  return 0 if($db_handle->{InactiveDestroy});

  #If any statement handles are still active, don't allow disconnection
  #In this case it is being called before a query has been fully processed
  #either by not reading all rows of data returned, or not calling ->finish
  #on the statement handle.  Don't disconnect, send warning
  if($db_handle->{ActiveKids} != 0) {
     warn("Problem disconnect : kids=",$db_handle->{Kids},
            " activekids=",$db_handle->{ActiveKids},"\n");
     return 1;
946
  }
947 948 949 950 951
  
  $db_handle->disconnect();
  $self->connected(undef);
  $self->disconnect_count($self->disconnect_count()+1);
  #print("DISCONNECT\n");
952
  $self->db_handle(undef);
953
  return 0;
954 955
}

956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978

=head2 add_limit_clause

  Arg [1]    : string $sql
  Arg [2]    : int $max_number
  Example    : my $new_sql = $dbc->add_limit_clause($sql,$max_number);
  Description: Giving an SQL statement, it adds a limit clause, dependent on the database 
               (in MySQL, should add a LIMIT at the end, MSSQL uses a TOP clause)									 
  Returntype : String containing the new valid SQL statement        
  Exceptions : none
  Caller     : general
  Status     : at risk

=cut


sub add_limit_clause{
    my $self = shift;
    my $sql = shift;
    my $max_number = shift;

    my $new_sql = '';
    if ($self->driver eq 'mysql'){
979
        $new_sql = $sql . ' LIMIT ' . $max_number;
980 981
    }
    elsif ($self->driver eq 'odbc'){
982 983 984
        #need to get anything after the SELECT statement
        $sql =~ /select(.*)/i;
        $new_sql = 'SELECT TOP ' . $max_number . $1;
985 986
    }
    else{
987 988
        warning("Not possible to convert $sql to an unknow database driver: ", $self->driver, " no limit applied");
        $new_sql = $sql;
989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012
    }
    return $new_sql;
}


=head2 from_date_to_seconds

  Arg [1]    : date $date
  Example    : my $string = $dbc->from_date_to_seconds($date);
  Description: Giving a string representing a column of type date 
                applies the database function to convert to the number of seconds from 01-01-1970
  Returntype : string
  Exceptions : none
  Caller     : general
  Status     : at risk

=cut

sub from_date_to_seconds{
    my $self=  shift;
    my $column = shift;

    my $string;
    if ($self->driver eq 'mysql'){
1013
        $string = "UNIX_TIMESTAMP($column)";
1014 1015
    }
    elsif ($self->driver eq 'odbc'){
1016
        $string = "DATEDIFF(second,'JAN 1 1970',$column)";
1017
    }
1018 1019 1020
    elsif ($self->driver eq 'SQLite'){
        $string = "STRFTIME('%s', $column)";
    }
1021
    else{
1022 1023
        warning("Not possible to convert $column due to an unknown database driver: ", $self->driver);
        return '';
1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047
    }    
    return $string;
}


=head2 from_seconds_to_date

  Arg [1]    : int $seconds
  Example    : my $string = $dbc->from_seconds_to_date($seconds);
  Description: Giving an int representing number of seconds
                applies the database function to convert to a date 
  Returntype : string
  Exceptions : none
  Caller     : general
  Status     : at risk

=cut

sub from_seconds_to_date{
    my $self=  shift;
    my $seconds = shift;

    my $string;
    if ($self->driver eq 'mysql'){
1048 1049 1050 1051 1052 1053
        if ($seconds){
            $string = "from_unixtime( ".$seconds.")";
        }
        else{
            $string = "\"0000-00-00 00:00:00\"";
        }
1054 1055
    }
    elsif ($self->driver eq 'odbc'){
1056