2003-01-14 20:00:11 +00:00
|
|
|
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
|
|
|
#
|
|
|
|
# The contents of this file are subject to the Mozilla Public
|
|
|
|
# License Version 1.1 (the "License"); you may not use this file
|
|
|
|
# except in compliance with the License. You may obtain a copy of
|
|
|
|
# the License at http://www.mozilla.org/MPL/
|
|
|
|
#
|
|
|
|
# Software distributed under the License is distributed on an "AS
|
|
|
|
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
|
|
|
# implied. See the License for the specific language governing
|
|
|
|
# rights and limitations under the License.
|
|
|
|
#
|
|
|
|
# The Original Code is the Bugzilla Bug Tracking System.
|
|
|
|
#
|
|
|
|
# The Initial Developer of the Original Code is Netscape Communications
|
|
|
|
# Corporation. Portions created by Netscape are
|
|
|
|
# Copyright (C) 1998 Netscape Communications Corporation. All
|
|
|
|
# Rights Reserved.
|
|
|
|
#
|
|
|
|
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
|
|
|
# Dan Mosedale <dmose@mozilla.org>
|
|
|
|
# Jacob Steenhagen <jake@bugzilla.org>
|
|
|
|
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
|
|
|
# Christopher Aillon <christopher@aillon.com>
|
2005-02-16 18:11:07 +00:00
|
|
|
# Tomas Kopal <Tomas.Kopal@altap.cz>
|
2003-01-14 20:00:11 +00:00
|
|
|
|
|
|
|
package Bugzilla::DB;
|
|
|
|
|
|
|
|
use strict;
|
|
|
|
|
|
|
|
use DBI;
|
|
|
|
|
2005-02-16 18:11:07 +00:00
|
|
|
# Inherit the DB class from DBI::db and Exporter
|
|
|
|
# Note that we inherit from Exporter just to allow the old, deprecated
|
|
|
|
# interface to work. If it gets removed, the Exporter class can be removed
|
|
|
|
# from this list.
|
|
|
|
use base qw(Exporter DBI::db);
|
2003-01-14 20:00:11 +00:00
|
|
|
|
|
|
|
%Bugzilla::DB::EXPORT_TAGS =
|
|
|
|
(
|
2004-03-27 03:51:44 +00:00
|
|
|
deprecated => [qw(SendSQL SqlQuote
|
2003-01-14 20:00:11 +00:00
|
|
|
MoreSQLData FetchSQLData FetchOneColumn
|
|
|
|
PushGlobalSQLState PopGlobalSQLState)
|
|
|
|
],
|
|
|
|
);
|
|
|
|
Exporter::export_ok_tags('deprecated');
|
|
|
|
|
|
|
|
use Bugzilla::Config qw(:DEFAULT :db);
|
|
|
|
use Bugzilla::Util;
|
2005-02-16 18:11:07 +00:00
|
|
|
use Bugzilla::Error;
|
2003-01-14 20:00:11 +00:00
|
|
|
|
|
|
|
# All this code is backwards compat fu. As such, its a bit ugly. Note the
|
2005-02-02 16:06:51 +00:00
|
|
|
# circular dependencies on Bugzilla.pm
|
2003-01-14 20:00:11 +00:00
|
|
|
# This is old cruft which will be removed, so theres not much use in
|
|
|
|
# having a separate package for it, or otherwise trying to avoid the circular
|
2005-02-02 16:06:51 +00:00
|
|
|
# dependency
|
2003-01-14 20:00:11 +00:00
|
|
|
|
|
|
|
# XXX - mod_perl
|
2003-02-14 10:37:43 +00:00
|
|
|
# These use |our| instead of |my| because they need to be cleared from
|
|
|
|
# Bugzilla.pm. See bug 192531 for details.
|
|
|
|
our $_current_sth;
|
|
|
|
our @SQLStateStack = ();
|
2003-01-14 20:00:11 +00:00
|
|
|
sub SendSQL {
|
|
|
|
my ($str) = @_;
|
|
|
|
|
2003-02-07 07:19:15 +00:00
|
|
|
$_current_sth = Bugzilla->dbh->prepare($str);
|
2003-02-14 10:37:43 +00:00
|
|
|
|
2003-02-20 07:07:42 +00:00
|
|
|
$_current_sth->execute;
|
|
|
|
|
2003-02-14 10:37:43 +00:00
|
|
|
# This is really really ugly, but its what we get for not doing
|
|
|
|
# error checking for 5 years. See bug 189446 and bug 192531
|
|
|
|
$_current_sth->{RaiseError} = 0;
|
2003-01-14 20:00:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
# Its much much better to use bound params instead of this
|
|
|
|
sub SqlQuote {
|
|
|
|
my ($str) = @_;
|
|
|
|
|
|
|
|
# Backwards compat code
|
2003-01-30 07:20:56 +00:00
|
|
|
return "''" if not defined $str;
|
2003-01-14 20:00:11 +00:00
|
|
|
|
2003-02-07 07:19:15 +00:00
|
|
|
my $res = Bugzilla->dbh->quote($str);
|
2003-01-14 20:00:11 +00:00
|
|
|
|
|
|
|
trick_taint($res);
|
|
|
|
|
|
|
|
return $res;
|
|
|
|
}
|
|
|
|
|
|
|
|
# XXX - mod_perl
|
|
|
|
my $_fetchahead;
|
|
|
|
sub MoreSQLData {
|
|
|
|
return 1 if defined $_fetchahead;
|
|
|
|
|
|
|
|
if ($_fetchahead = $_current_sth->fetchrow_arrayref()) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub FetchSQLData {
|
|
|
|
if (defined $_fetchahead) {
|
|
|
|
my @result = @$_fetchahead;
|
|
|
|
undef $_fetchahead;
|
|
|
|
return @result;
|
|
|
|
}
|
2003-01-23 07:11:42 +00:00
|
|
|
|
2003-02-14 10:37:43 +00:00
|
|
|
return $_current_sth->fetchrow_array;
|
2003-01-14 20:00:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
sub FetchOneColumn {
|
|
|
|
my @row = FetchSQLData();
|
|
|
|
return $row[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
sub PushGlobalSQLState() {
|
|
|
|
push @SQLStateStack, $_current_sth;
|
|
|
|
push @SQLStateStack, $_fetchahead;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub PopGlobalSQLState() {
|
|
|
|
die ("PopGlobalSQLState: stack underflow") if ( scalar(@SQLStateStack) < 1 );
|
|
|
|
$_fetchahead = pop @SQLStateStack;
|
|
|
|
$_current_sth = pop @SQLStateStack;
|
|
|
|
}
|
|
|
|
|
|
|
|
# MODERN CODE BELOW
|
|
|
|
|
|
|
|
sub connect_shadow {
|
|
|
|
die "Tried to connect to non-existent shadowdb" unless Param('shadowdb');
|
|
|
|
|
2005-02-16 18:11:07 +00:00
|
|
|
return _connect($db_driver, Param("shadowdbhost"),
|
|
|
|
Param('shadowdb'), Param("shadowdbport"),
|
|
|
|
Param("shadowdbsock"), $db_user, $db_pass);
|
2003-01-14 20:00:11 +00:00
|
|
|
}
|
|
|
|
|
2005-02-18 18:53:17 +00:00
|
|
|
sub connect_main (;$) {
|
|
|
|
my ($no_db_name) = @_;
|
|
|
|
my $connect_to_db = $db_name;
|
|
|
|
$connect_to_db = "" if $no_db_name;
|
|
|
|
return _connect($db_driver, $db_host, $connect_to_db, $db_port,
|
2005-02-16 18:11:07 +00:00
|
|
|
$db_sock, $db_user, $db_pass);
|
2003-01-14 20:00:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
sub _connect {
|
2005-02-16 18:11:07 +00:00
|
|
|
my ($driver, $host, $dbname, $port, $sock, $user, $pass) = @_;
|
2003-01-14 20:00:11 +00:00
|
|
|
|
2005-02-16 18:11:07 +00:00
|
|
|
# DB specific module have the same name as DB driver, here we
|
|
|
|
# just make sure we are not case sensitive
|
|
|
|
(my $db_module = $driver) =~ s/(\w+)/\u\L$1/g;
|
|
|
|
my $pkg_module = "Bugzilla::DB::" . $db_module;
|
|
|
|
|
|
|
|
# do the actual import
|
2005-02-18 18:53:17 +00:00
|
|
|
eval ("require $pkg_module")
|
|
|
|
|| die ("'$db_module' is not a valid choice for \$db_driver in "
|
|
|
|
. " localconfig: " . $@);
|
2005-02-16 18:11:07 +00:00
|
|
|
|
|
|
|
# instantiate the correct DB specific module
|
|
|
|
my $dbh = $pkg_module->new($user, $pass, $host, $dbname, $port, $sock);
|
2003-01-14 20:00:11 +00:00
|
|
|
|
|
|
|
return $dbh;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub _handle_error {
|
|
|
|
require Carp;
|
|
|
|
|
2004-12-06 17:16:33 +00:00
|
|
|
# Cut down the error string to a reasonable size
|
|
|
|
$_[0] = substr($_[0], 0, 2000) . ' ... ' . substr($_[0], -2000)
|
|
|
|
if length($_[0]) > 4000;
|
2003-01-14 20:00:11 +00:00
|
|
|
$_[0] = Carp::longmess($_[0]);
|
|
|
|
return 0; # Now let DBI handle raising the error
|
|
|
|
}
|
|
|
|
|
2005-02-16 18:11:07 +00:00
|
|
|
# List of abstract methods we are checking the derived class implements
|
|
|
|
our @_abstract_methods = qw(new sql_regexp sql_not_regexp sql_limit
|
|
|
|
sql_to_days sql_date_format sql_interval
|
|
|
|
bz_lock_tables bz_unlock_tables);
|
|
|
|
|
|
|
|
# This overriden import method will check implementation of inherited classes
|
|
|
|
# for missing implementation of abstract methods
|
|
|
|
# See http://perlmonks.thepen.com/44265.html
|
|
|
|
sub import {
|
|
|
|
my $pkg = shift;
|
|
|
|
|
|
|
|
# do not check this module
|
|
|
|
if ($pkg ne __PACKAGE__) {
|
|
|
|
# make sure all abstract methods are implemented
|
|
|
|
foreach my $meth (@_abstract_methods) {
|
|
|
|
$pkg->can($meth)
|
|
|
|
or croak("Class $pkg does not define method $meth");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# Now we want to call our superclass implementation.
|
|
|
|
# If our superclass is Exporter, which is using caller() to find
|
|
|
|
# a namespace to populate, we need to adjust for this extra call.
|
|
|
|
# All this can go when we stop using deprecated functions.
|
|
|
|
my $is_exporter = $pkg->isa('Exporter');
|
|
|
|
$Exporter::ExportLevel++ if $is_exporter;
|
|
|
|
$pkg->SUPER::import(@_);
|
|
|
|
$Exporter::ExportLevel-- if $is_exporter;
|
|
|
|
}
|
|
|
|
|
|
|
|
# note that when multiple databases are supported, version number does not
|
2005-02-16 22:56:31 +00:00
|
|
|
# make sense anymore (as it is DB-dependent). This needs to be removed in
|
2005-02-16 18:11:07 +00:00
|
|
|
# the future and places where it's used fixed.
|
2004-05-27 02:40:18 +00:00
|
|
|
my $cached_server_version;
|
|
|
|
sub server_version {
|
|
|
|
return $cached_server_version if defined($cached_server_version);
|
|
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
my $sth = $dbh->prepare('SELECT VERSION()');
|
|
|
|
$sth->execute();
|
|
|
|
($cached_server_version) = $sth->fetchrow_array();
|
|
|
|
return $cached_server_version;
|
|
|
|
}
|
|
|
|
|
2005-02-16 18:11:07 +00:00
|
|
|
sub bz_get_field_defs {
|
|
|
|
my ($self) = @_;
|
2005-02-01 16:26:26 +00:00
|
|
|
|
|
|
|
my $extra = "";
|
|
|
|
if (!&::UserInGroup(Param('timetrackinggroup'))) {
|
2005-02-25 03:16:47 +00:00
|
|
|
$extra = "AND name NOT IN ('estimated_time', 'remaining_time', " .
|
2005-02-01 16:26:26 +00:00
|
|
|
"'work_time', 'percentage_complete', 'deadline')";
|
|
|
|
}
|
|
|
|
|
|
|
|
my @fields;
|
2005-02-25 03:16:47 +00:00
|
|
|
my $sth = $self->prepare("SELECT name, description FROM fielddefs
|
|
|
|
WHERE obsolete = 0 $extra
|
|
|
|
ORDER BY sortkey");
|
2005-02-01 16:26:26 +00:00
|
|
|
$sth->execute();
|
|
|
|
while (my $field_ref = $sth->fetchrow_hashref()) {
|
|
|
|
push(@fields, $field_ref);
|
|
|
|
}
|
|
|
|
return(@fields);
|
|
|
|
}
|
|
|
|
|
2005-03-01 21:19:20 +00:00
|
|
|
# XXX - Need to make this cross-db compatible
|
|
|
|
# XXX - This shouldn't print stuff to stdout
|
|
|
|
sub bz_add_field ($$$) {
|
|
|
|
my ($self, $table, $field, $definition) = @_;
|
|
|
|
|
|
|
|
my $ref = $self->bz_get_field_def($table, $field);
|
|
|
|
return if $ref; # already added?
|
|
|
|
|
|
|
|
print "Adding new field $field to table $table ...\n";
|
|
|
|
$self->do("ALTER TABLE $table
|
|
|
|
ADD COLUMN $field $definition");
|
|
|
|
}
|
|
|
|
|
|
|
|
# XXX - Need to make this cross-db compatible
|
|
|
|
# XXX - This shouldn't print stuff to stdout
|
|
|
|
sub bz_change_field_type ($$$) {
|
|
|
|
my ($self, $table, $field, $newtype) = @_;
|
|
|
|
|
|
|
|
my $ref = $self->bz_get_field_def($table, $field);
|
|
|
|
|
|
|
|
my $oldtype = $ref->[1];
|
|
|
|
if (! $ref->[2]) {
|
|
|
|
$oldtype .= qq{ not null};
|
|
|
|
}
|
|
|
|
if ($ref->[4]) {
|
|
|
|
$oldtype .= qq{ default "$ref->[4]"};
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($oldtype ne $newtype) {
|
|
|
|
print "Updating field type $field in table $table ...\n";
|
|
|
|
print "old: $oldtype\n";
|
|
|
|
print "new: $newtype\n";
|
|
|
|
$self->do("ALTER TABLE $table
|
|
|
|
CHANGE $field
|
|
|
|
$field $newtype");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# XXX - Need to make this cross-db compatible
|
|
|
|
# XXX - This shouldn't print stuff to stdout
|
|
|
|
sub bz_drop_field ($$) {
|
|
|
|
my ($self, $table, $field) = @_;
|
|
|
|
|
|
|
|
my $ref = $self->bz_get_field_def($table, $field);
|
|
|
|
return unless $ref; # already dropped?
|
|
|
|
|
|
|
|
print "Deleting unused field $field from table $table ...\n";
|
|
|
|
$self->do("ALTER TABLE $table
|
|
|
|
DROP COLUMN $field");
|
|
|
|
}
|
|
|
|
|
|
|
|
# XXX - Needs to be made cross-db compatible
|
|
|
|
sub bz_drop_table_indexes ($) {
|
|
|
|
my ($self, $table) = @_;
|
|
|
|
my %seen;
|
|
|
|
|
|
|
|
# get the list of indexes
|
|
|
|
my $sth = $self->prepare("SHOW INDEX FROM $table");
|
|
|
|
$sth->execute;
|
|
|
|
|
|
|
|
# drop each index
|
|
|
|
while ( my $ref = $sth->fetchrow_arrayref) {
|
|
|
|
|
|
|
|
# note that some indexes are described by multiple rows in the
|
|
|
|
# index table, so we may have already dropped the index described
|
|
|
|
# in the current row.
|
|
|
|
next if exists $seen{$$ref[2]};
|
|
|
|
|
|
|
|
if ($$ref[2] eq 'PRIMARY') {
|
|
|
|
# The syntax for dropping a PRIMARY KEY is different
|
|
|
|
# from the normal DROP INDEX syntax.
|
|
|
|
$self->do("ALTER TABLE $table DROP PRIMARY KEY");
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$self->do("ALTER TABLE $table DROP INDEX $$ref[2]");
|
|
|
|
}
|
|
|
|
$seen{$$ref[2]} = 1;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# XXX - Needs to be made cross-db compatible
|
|
|
|
sub bz_rename_field ($$$) {
|
|
|
|
my ($self, $table, $field, $newname) = @_;
|
|
|
|
|
|
|
|
my $ref = $self->bz_get_field_def($table, $field);
|
|
|
|
return unless $ref; # already renamed?
|
|
|
|
|
|
|
|
if ($$ref[1] ne $newname) {
|
|
|
|
print "Updating field $field in table $table ...\n";
|
|
|
|
my $type = $$ref[1];
|
|
|
|
$type .= " NOT NULL" if !$$ref[2];
|
|
|
|
$type .= " auto_increment" if $$ref[5] =~ /auto_increment/;
|
|
|
|
$self->do("ALTER TABLE $table
|
|
|
|
CHANGE $field
|
|
|
|
$newname $type");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# XXX - Needs to be made cross-db compatible.
|
|
|
|
sub bz_get_field_def ($$) {
|
|
|
|
my ($self, $table, $field) = @_;
|
|
|
|
my $sth = $self->prepare("SHOW COLUMNS FROM $table");
|
|
|
|
$sth->execute;
|
|
|
|
|
|
|
|
while (my $ref = $sth->fetchrow_arrayref) {
|
|
|
|
next if $$ref[0] ne $field;
|
|
|
|
return $ref;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# XXX - Needs to be made cross-db compatible
|
|
|
|
sub bz_get_index_count ($) {
|
|
|
|
my ($self, $table) = @_;
|
|
|
|
|
|
|
|
my $sth = $self->prepare("SHOW INDEX FROM $table");
|
|
|
|
$sth->execute;
|
|
|
|
|
|
|
|
if ( $sth->rows == -1 ) {
|
|
|
|
die ("Unexpected response while counting indexes in $table:" .
|
|
|
|
" \$sth->rows == -1");
|
|
|
|
}
|
|
|
|
|
|
|
|
return ($sth->rows);
|
|
|
|
}
|
|
|
|
|
|
|
|
# XXX - Needs to be made cross-db compatible.
|
|
|
|
sub bz_get_index_def ($$) {
|
|
|
|
my ($self, $table, $field) = @_;
|
|
|
|
my $sth = $self->prepare("SHOW INDEX FROM $table");
|
|
|
|
$sth->execute;
|
|
|
|
|
|
|
|
while (my $ref = $sth->fetchrow_arrayref) {
|
|
|
|
next if $$ref[2] ne $field;
|
|
|
|
return $ref;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# XXX - Needs to be made cross-db compatible
|
|
|
|
sub bz_table_exists ($) {
|
|
|
|
my ($self, $table) = @_;
|
|
|
|
my $exists = 0;
|
|
|
|
my $sth = $self->prepare("SHOW TABLES");
|
|
|
|
$sth->execute;
|
|
|
|
while (my ($dbtable) = $sth->fetchrow_array ) {
|
|
|
|
if ($dbtable eq $table) {
|
|
|
|
$exists = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $exists;
|
|
|
|
}
|
|
|
|
|
2005-02-16 18:11:07 +00:00
|
|
|
sub bz_last_key {
|
|
|
|
my ($self, $table, $column) = @_;
|
|
|
|
|
|
|
|
return $self->last_insert_id($db_name, undef, $table, $column);
|
|
|
|
}
|
|
|
|
|
|
|
|
sub bz_start_transaction {
|
|
|
|
my ($self) = @_;
|
|
|
|
|
|
|
|
if ($self->{private_bz_in_transaction}) {
|
|
|
|
carp("Can't start transaction within another transaction");
|
|
|
|
ThrowCodeError("nested_transaction");
|
|
|
|
} else {
|
|
|
|
# Turn AutoCommit off and start a new transaction
|
|
|
|
$self->begin_work();
|
|
|
|
|
|
|
|
$self->{privateprivate_bz_in_transaction} = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sub bz_commit_transaction {
|
|
|
|
my ($self) = @_;
|
|
|
|
|
|
|
|
if (!$self->{private_bz_in_transaction}) {
|
|
|
|
carp("Can't commit without a transaction");
|
|
|
|
ThrowCodeError("not_in_transaction");
|
|
|
|
} else {
|
|
|
|
$self->commit();
|
|
|
|
|
|
|
|
$self->{private_bz_in_transaction} = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sub bz_rollback_transaction {
|
|
|
|
my ($self) = @_;
|
|
|
|
|
|
|
|
if (!$self->{private_bz_in_transaction}) {
|
|
|
|
carp("Can't rollback without a transaction");
|
|
|
|
ThrowCodeError("not_in_transaction");
|
|
|
|
} else {
|
|
|
|
$self->rollback();
|
|
|
|
|
|
|
|
$self->{private_bz_in_transaction} = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sub db_new {
|
|
|
|
my ($class, $dsn, $user, $pass, $attributes) = @_;
|
|
|
|
|
|
|
|
# set up default attributes used to connect to the database
|
|
|
|
# (if not defined by DB specific implementation)
|
2005-02-18 18:53:17 +00:00
|
|
|
$attributes = { RaiseError => 0,
|
2005-02-16 18:11:07 +00:00
|
|
|
AutoCommit => 1,
|
|
|
|
PrintError => 0,
|
|
|
|
ShowErrorStatement => 1,
|
|
|
|
HandleError => \&_handle_error,
|
|
|
|
TaintIn => 1,
|
|
|
|
FetchHashKeyName => 'NAME',
|
|
|
|
# Note: NAME_lc causes crash on ActiveState Perl
|
|
|
|
# 5.8.4 (see Bug 253696)
|
|
|
|
# XXX - This will likely cause problems in DB
|
|
|
|
# back ends that twiddle column case (Oracle?)
|
|
|
|
} if (!defined($attributes));
|
|
|
|
|
|
|
|
# connect using our known info to the specified db
|
|
|
|
# Apache::DBI will cache this when using mod_perl
|
2005-02-18 18:53:17 +00:00
|
|
|
my $self = DBI->connect($dsn, $user, $pass, $attributes)
|
|
|
|
or die "\nCan't connect to the database.\nError: $DBI::errstr\n"
|
|
|
|
. " Is your database installed and up and running?\n Do you have"
|
|
|
|
. "the correct username and password selected in localconfig?\n\n";
|
|
|
|
|
|
|
|
# RaiseError was only set to 0 so that we could catch the
|
|
|
|
# above "die" condition.
|
|
|
|
$self->{RaiseError} = 1;
|
2005-02-16 18:11:07 +00:00
|
|
|
|
|
|
|
# class variables
|
|
|
|
$self->{private_bz_in_transaction} = 0;
|
|
|
|
|
|
|
|
bless ($self, $class);
|
|
|
|
|
|
|
|
return $self;
|
|
|
|
}
|
|
|
|
|
2003-01-14 20:00:11 +00:00
|
|
|
1;
|
|
|
|
|
|
|
|
__END__
|
|
|
|
|
|
|
|
=head1 NAME
|
|
|
|
|
|
|
|
Bugzilla::DB - Database access routines, using L<DBI>
|
|
|
|
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
|
2005-02-16 18:11:07 +00:00
|
|
|
# Obtain db handle
|
|
|
|
use Bugzilla::DB;
|
|
|
|
my $dbh = Bugzilla->dbh;
|
2003-01-14 20:00:11 +00:00
|
|
|
|
2005-02-16 18:11:07 +00:00
|
|
|
# prepare a query using DB methods
|
|
|
|
my $sth = $dbh->prepare("SELECT " .
|
|
|
|
$dbh->sql_date_format("creation_ts", "%Y%m%d") .
|
|
|
|
" FROM bugs WHERE bug_status != 'RESOLVED' " .
|
|
|
|
$dbh->sql_limit(1));
|
|
|
|
|
|
|
|
# Execute the query
|
|
|
|
$sth->execute;
|
|
|
|
|
|
|
|
# Get the results
|
|
|
|
my @result = $sth->fetchrow_array;
|
2005-02-01 16:26:26 +00:00
|
|
|
|
2005-03-01 21:19:20 +00:00
|
|
|
# Schema Changes
|
|
|
|
$dbh->bz_add_field($table, $column, $definition);
|
|
|
|
$dbh->bz_change_field_type($table, $column, $newtype);
|
|
|
|
$dbh->bz_drop_field($table, $column);
|
|
|
|
$dbh->bz_drop_table_indexes($table);
|
|
|
|
$dbh->bz_rename_field($table, $column, $newname);
|
|
|
|
|
2005-02-16 18:11:07 +00:00
|
|
|
# Schema Information
|
2005-03-01 21:19:20 +00:00
|
|
|
my @fields = $dbh->bz_get_field_defs();
|
|
|
|
my @fieldinfo = $dbh->bz_get_field_def($table, $column);
|
|
|
|
my @indexinfo = $dbh->bz_get_index_def($table, $index);
|
|
|
|
my $exists = $dbh->bz_table_exists($table);
|
2003-01-14 20:00:11 +00:00
|
|
|
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
|
2005-02-16 18:11:07 +00:00
|
|
|
Functions in this module allows creation of a database handle to connect
|
|
|
|
to the Bugzilla database. This should never be done directly; all users
|
|
|
|
should use the L<Bugzilla> module to access the current C<dbh> instead.
|
|
|
|
|
|
|
|
This module also contains methods extending the returned handle with
|
|
|
|
functionality which is different between databases allowing for easy
|
|
|
|
customization for particular database via inheritance. These methods
|
|
|
|
should be always preffered over hard-coding SQL commands.
|
2003-01-14 20:00:11 +00:00
|
|
|
|
|
|
|
Access to the old SendSQL-based database routines are also provided by
|
|
|
|
importing the C<:deprecated> tag. These routines should not be used in new
|
|
|
|
code.
|
|
|
|
|
|
|
|
=head1 CONNECTION
|
|
|
|
|
|
|
|
A new database handle to the required database can be created using this
|
|
|
|
module. This is normally done by the L<Bugzilla> module, and so these routines
|
|
|
|
should not be called from anywhere else.
|
|
|
|
|
2005-02-16 18:11:07 +00:00
|
|
|
=head2 Functions
|
|
|
|
|
2003-01-14 20:00:11 +00:00
|
|
|
=over 4
|
|
|
|
|
|
|
|
=item C<connect_main>
|
|
|
|
|
2005-02-16 18:11:07 +00:00
|
|
|
Description: Function to connect to the main database, returning a new
|
|
|
|
database handle.
|
2005-02-18 18:53:17 +00:00
|
|
|
Params: $no_db_name (optional) - If true, Connect to the database
|
|
|
|
server, but don't connect to a specific database. This
|
|
|
|
is only used when creating a database. After you create
|
|
|
|
the database, you should re-create a new Bugzilla::DB object
|
|
|
|
without using this parameter.
|
2005-02-16 18:11:07 +00:00
|
|
|
Returns: new instance of the DB class
|
2003-01-14 20:00:11 +00:00
|
|
|
|
|
|
|
=item C<connect_shadow>
|
|
|
|
|
2005-02-16 18:11:07 +00:00
|
|
|
Description: Function to connect to the shadow database, returning a new
|
|
|
|
database handle.
|
|
|
|
This routine C<die>s if no shadow database is configured.
|
|
|
|
Params: none
|
|
|
|
Returns: new instance of the DB class
|
|
|
|
|
|
|
|
=item C<_connect>
|
|
|
|
|
|
|
|
Description: Internal function, creates and returns a new, connected
|
|
|
|
instance of the correct DB class.
|
|
|
|
This routine C<die>s if no driver is specified.
|
|
|
|
Params: $driver = name of the database driver to use
|
|
|
|
$host = host running the database we are connecting to
|
|
|
|
$dbname = name of the database to connect to
|
|
|
|
$port = port the database is listening on
|
|
|
|
$sock = socket the database is listening on
|
|
|
|
$user = username used to log in to the database
|
|
|
|
$pass = password used to log in to the database
|
|
|
|
Returns: new instance of the DB class
|
|
|
|
|
|
|
|
=item C<_handle_error>
|
|
|
|
|
|
|
|
Description: Function passed to the DBI::connect call for error handling.
|
|
|
|
It shortens the error for printing.
|
|
|
|
|
|
|
|
=item C<import>
|
|
|
|
|
|
|
|
Description: Overrides the standard import method to check that derived class
|
|
|
|
implements all required abstract methods. Also calls original
|
|
|
|
implementation in its super class.
|
2003-01-14 20:00:11 +00:00
|
|
|
|
|
|
|
=back
|
|
|
|
|
2005-02-16 18:11:07 +00:00
|
|
|
=head2 Methods
|
2005-02-01 16:26:26 +00:00
|
|
|
|
2005-02-16 18:11:07 +00:00
|
|
|
Note: Methods which can be implemented generically for all DBs are implemented in
|
|
|
|
this module. If needed, they can be overriden with DB specific code.
|
|
|
|
Methods which do not have standard implementation are abstract and must
|
|
|
|
be implemented for all supported databases separately.
|
|
|
|
To avoid confusion with standard DBI methods, all methods returning string with
|
|
|
|
formatted SQL command have prefix C<sql_>. All other methods have prefix C<bz_>.
|
2005-02-01 16:26:26 +00:00
|
|
|
|
|
|
|
=over 4
|
|
|
|
|
2005-02-16 18:11:07 +00:00
|
|
|
=item C<new>
|
|
|
|
|
|
|
|
Description: Constructor
|
|
|
|
Abstract method, should be overriden by database specific code.
|
|
|
|
Params: $user = username used to log in to the database
|
|
|
|
$pass = password used to log in to the database
|
|
|
|
$host = host running the database we are connecting to
|
|
|
|
$dbname = name of the database to connect to
|
|
|
|
$port = port the database is listening on
|
|
|
|
$sock = socket the database is listening on
|
|
|
|
Returns: new instance of the DB class
|
|
|
|
Note: The constructor should create a DSN from the parameters provided and
|
|
|
|
then call C<db_new()> method of its super class to create a new
|
|
|
|
class instance. See C<db_new> description in this module. As per
|
|
|
|
DBI documentation, all class variables must be prefixed with
|
|
|
|
"private_". See L<DBI>.
|
|
|
|
|
|
|
|
=item C<sql_regexp>
|
|
|
|
|
|
|
|
Description: Outputs SQL regular expression operator for POSIX regex
|
|
|
|
searches in format suitable for a given database.
|
|
|
|
Abstract method, should be overriden by database specific code.
|
|
|
|
Params: none
|
|
|
|
Returns: formatted SQL for regular expression search (e.g. REGEXP)
|
|
|
|
(scalar)
|
|
|
|
|
|
|
|
=item C<sql_not_regexp>
|
|
|
|
|
|
|
|
Description: Outputs SQL regular expression operator for negative POSIX
|
|
|
|
regex searches in format suitable for a given database.
|
|
|
|
Abstract method, should be overriden by database specific code.
|
|
|
|
Params: none
|
|
|
|
Returns: formatted SQL for negative regular expression search
|
|
|
|
(e.g. NOT REGEXP) (scalar)
|
|
|
|
|
|
|
|
=item C<sql_limit>
|
|
|
|
|
|
|
|
Description: Returns SQL syntax for limiting results to some number of rows
|
|
|
|
with optional offset if not starting from the begining.
|
|
|
|
Abstract method, should be overriden by database specific code.
|
|
|
|
Params: $limit = number of rows to return from query (scalar)
|
|
|
|
$offset = number of rows to skip prior counting (scalar)
|
|
|
|
Returns: formatted SQL for limiting number of rows returned from query
|
|
|
|
with optional offset (e.g. LIMIT 1, 1) (scalar)
|
|
|
|
|
|
|
|
=item C<sql_to_days>
|
|
|
|
|
|
|
|
Description: Outputs SQL syntax for converting date to Julian days.
|
|
|
|
Abstract method, should be overriden by database specific code.
|
|
|
|
Params: $date = date to convert to days
|
|
|
|
Returns: formatted SQL for returning date fields in Julian days. (scalar)
|
|
|
|
|
|
|
|
=item C<sql_date_format>
|
|
|
|
|
|
|
|
Description: Outputs SQL syntax for formatting dates.
|
|
|
|
Abstract method, should be overriden by database specific code.
|
|
|
|
Params: $date = date or name of date type column (scalar)
|
|
|
|
$format = format string for date output (scalar)
|
|
|
|
(%Y = year, four digits, %y = year, two digits, %m = month,
|
|
|
|
%d = day, %a = weekday name, 3 letters, %H = hour 00-23,
|
|
|
|
%i = minute, %s = second)
|
|
|
|
Returns: formatted SQL for date formatting (scalar)
|
|
|
|
|
|
|
|
=item C<sql_interval>
|
|
|
|
|
|
|
|
Description: Outputs proper SQL syntax for a time interval function.
|
|
|
|
Abstract method, should be overriden by database specific code.
|
|
|
|
Params: $interval = the time interval requested (e.g. '30 minutes')
|
|
|
|
(scalar)
|
|
|
|
Returns: formatted SQL for interval function (scalar)
|
|
|
|
|
|
|
|
=item C<bz_lock_tables>
|
|
|
|
|
|
|
|
Description: Performs a table lock operation on specified tables.
|
|
|
|
If the underlying database supports transactions, it should also
|
|
|
|
implicitly start a new transaction.
|
|
|
|
Abstract method, should be overriden by database specific code.
|
|
|
|
Params: @tables = list of names of tables to lock in MySQL
|
|
|
|
notation (ex. 'bugs AS bugs2 READ', 'logincookies WRITE')
|
|
|
|
Returns: none
|
|
|
|
|
|
|
|
=item C<bz_unlock_tables>
|
|
|
|
|
|
|
|
Description: Performs a table unlock operation
|
|
|
|
If the underlying database supports transactions, it should also
|
|
|
|
implicitly commit or rollback the transaction.
|
|
|
|
Also, this function should allow to be called with the abort flag
|
|
|
|
set even without locking tables first without raising an error
|
|
|
|
to simplify error handling.
|
|
|
|
Abstract method, should be overriden by database specific code.
|
2005-02-17 21:57:27 +00:00
|
|
|
Params: $abort = UNLOCK_ABORT (true, 1) if the operation on locked tables
|
|
|
|
failed (if transactions are supported, the action will be rolled
|
2005-02-16 18:11:07 +00:00
|
|
|
back). False (0) or no param if the operation succeeded.
|
|
|
|
Returns: none
|
|
|
|
|
|
|
|
=item C<bz_last_key>
|
|
|
|
|
|
|
|
Description: Returns the last serial number, usually from a previous INSERT.
|
|
|
|
Must be executed directly following the relevant INSERT.
|
|
|
|
This base implementation uses DBI->last_insert_id. If the
|
|
|
|
DBD supports it, it is the preffered way to obtain the last
|
|
|
|
serial index. If it is not supported, the DB specific code
|
|
|
|
needs to override it with DB specific code.
|
|
|
|
Params: $table = name of table containing serial column (scalar)
|
|
|
|
$column = name of column containing serial data type (scalar)
|
|
|
|
Returns: Last inserted ID (scalar)
|
|
|
|
|
2005-03-01 21:19:20 +00:00
|
|
|
=item C<bz_add_field>
|
|
|
|
|
|
|
|
Description: Adds a new column to a table in the database. Prints out
|
|
|
|
a brief statement that it did so, to stdout.
|
|
|
|
Params: $table = the table where the column is being added
|
|
|
|
$column = the name of the new column
|
|
|
|
$definition = SQL for defining the new column
|
|
|
|
Returns: none
|
|
|
|
|
|
|
|
=item C<bz_change_field_type>
|
|
|
|
|
|
|
|
Description: Changes the data type of a column in a table. Prints out
|
|
|
|
the changes being made to stdout. If the new type is the
|
|
|
|
same as the old type, the function returns without changing
|
|
|
|
anything.
|
|
|
|
Params: $table = the table where the column is
|
|
|
|
$column = the name of the column you want to change
|
|
|
|
$newtype = the new data type of the column, in SQL format.
|
|
|
|
Returns: none
|
|
|
|
|
|
|
|
=item C<bz_drop_field>
|
|
|
|
|
|
|
|
Description: Removes a column from a database table. If the column
|
|
|
|
doesn't exist, we return without doing anything. If we do
|
|
|
|
anything, we print a short message to stdout about the change.
|
|
|
|
Params: $table = The table where the column is
|
|
|
|
$column = The name of the column you want to drop
|
|
|
|
Returns: none
|
|
|
|
|
|
|
|
=item C<bz_drop_table_indexes>
|
|
|
|
|
|
|
|
Description: Drops all indexes on a given table.
|
|
|
|
Params: $table = the table on which you wish to remove all indexes
|
|
|
|
Returns: none
|
|
|
|
|
|
|
|
=item C<bz_rename_field>
|
|
|
|
|
|
|
|
Description: Renames a column in a database table. If the column
|
|
|
|
doesn't exist, or if the new name is the same as the
|
|
|
|
old name, we return without changing anything.
|
|
|
|
Params: $table = the table where the column is that you want to rename
|
|
|
|
$column = the name of the column you want to rename
|
|
|
|
$newname = the new name of the column
|
|
|
|
Returns: none
|
|
|
|
|
2005-02-16 18:11:07 +00:00
|
|
|
=item C<bz_get_field_defs>
|
|
|
|
|
|
|
|
Description: Returns a list of all the "bug" fields in Bugzilla. The list
|
|
|
|
contains hashes, with a 'name' key and a 'description' key.
|
|
|
|
Params: none
|
|
|
|
Returns: List of all the "bug" fields
|
|
|
|
|
2005-03-01 21:19:20 +00:00
|
|
|
=item C<bz_get_field_def>
|
|
|
|
|
|
|
|
Description: Returns information about a column in a table in the database.
|
|
|
|
Params: $table = name of table containing the column (scalar)
|
|
|
|
$column = column you want info about (scalar)
|
|
|
|
Returns: An reference to an array containing information about the
|
|
|
|
field, with the following information in each array place:
|
|
|
|
0 = column name
|
|
|
|
1 = column type
|
|
|
|
2 = 'YES' if the column can be NULL, empty string otherwise
|
|
|
|
3 = The type of key, either 'MUL', 'UNI', 'PRI, or ''.
|
|
|
|
4 = The default value
|
|
|
|
5 = An "extra" column, per MySQL docs. Don't use it.
|
|
|
|
If the column does not exist, the function returns undef.
|
|
|
|
|
|
|
|
=item C<bz_get_index_count>
|
|
|
|
|
|
|
|
Description: Returns the number of indexes on a certain table.
|
|
|
|
Params: $table = the table that you want to count indexes on
|
|
|
|
Returns: The number of indexes on the table.
|
|
|
|
|
|
|
|
=item C<bz_get_index_def>
|
|
|
|
|
|
|
|
Description: Returns information about an index on a table in the database.
|
|
|
|
Params: $table = name of table containing the index (scalar)
|
|
|
|
$index = name of the index (scalar)
|
|
|
|
Returns: A reference to an array containing information about the
|
|
|
|
index, with the following information in each array place:
|
|
|
|
0 = name of the table that the index is on
|
|
|
|
1 = 0 if unique, 1 if not unique
|
|
|
|
2 = name of the index
|
|
|
|
3 = seq_in_index (either 1 or 0)
|
|
|
|
4 = Name of ONE column that the index is on
|
|
|
|
5 = 'Collation' of the index. Usually 'A'.
|
|
|
|
6 = Cardinality. Either a number or undef.
|
|
|
|
7 = sub_part. Usually undef. Sometimes 1.
|
|
|
|
8 = "packed". Usually undef.
|
|
|
|
9 = comments. Usually an empty string. Sometimes 'FULLTEXT'.
|
|
|
|
If the index does not exist, the function returns undef.
|
|
|
|
|
|
|
|
=item C<bz_table_exists>
|
|
|
|
|
|
|
|
Description: Returns whether or not the specified table exists in the DB.
|
|
|
|
Params: $table = the name of the table you're checking the existence
|
|
|
|
of (scalar)
|
|
|
|
Returns: A true value if the table exists, a false value otherwise.
|
|
|
|
|
2005-02-16 18:11:07 +00:00
|
|
|
=item C<bz_start_transaction>
|
|
|
|
|
|
|
|
Description: Starts a transaction if supported by the database being used
|
|
|
|
Params: none
|
|
|
|
Returns: none
|
|
|
|
|
|
|
|
=item C<bz_commit_transaction>
|
|
|
|
|
|
|
|
Description: Ends a transaction, commiting all changes, if supported by
|
|
|
|
the database being used
|
|
|
|
Params: none
|
|
|
|
Returns: none
|
|
|
|
|
|
|
|
=item C<bz_rollback_transaction>
|
|
|
|
|
|
|
|
Description: Ends a transaction, rolling back all changes, if supported by
|
|
|
|
the database being used
|
|
|
|
Params: none
|
|
|
|
Returns: none
|
|
|
|
|
|
|
|
=item C<db_new>
|
|
|
|
|
|
|
|
Description: Constructor
|
|
|
|
Params: $dsn = database connection string
|
|
|
|
$user = username used to log in to the database
|
|
|
|
$pass = password used to log in to the database
|
|
|
|
$attributes = set of attributes for DB connection (optional)
|
|
|
|
Returns: new instance of the DB class
|
|
|
|
Note: the name of this constructor is not new, as that would make
|
|
|
|
our check for implementation of new() by derived class useles.
|
2005-02-01 16:26:26 +00:00
|
|
|
|
2005-02-16 18:11:07 +00:00
|
|
|
=back
|
2005-02-01 16:26:26 +00:00
|
|
|
|
2003-01-14 20:00:11 +00:00
|
|
|
=head1 DEPRECATED ROUTINES
|
|
|
|
|
|
|
|
Several database routines are deprecated. They should not be used in new code,
|
|
|
|
and so are not documented.
|
|
|
|
|
|
|
|
=over 4
|
|
|
|
|
|
|
|
=item *
|
|
|
|
|
|
|
|
SendSQL
|
|
|
|
|
|
|
|
=item *
|
|
|
|
|
|
|
|
SqlQuote
|
|
|
|
|
|
|
|
=item *
|
|
|
|
|
|
|
|
MoreSQLData
|
|
|
|
|
|
|
|
=item *
|
|
|
|
|
|
|
|
FetchSQLData
|
|
|
|
|
|
|
|
=item *
|
|
|
|
|
|
|
|
FetchOneColumn
|
|
|
|
|
|
|
|
=item *
|
|
|
|
|
|
|
|
PushGlobalSQLState
|
|
|
|
|
|
|
|
=item *
|
|
|
|
|
|
|
|
PopGlobalSQLState
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
=head1 SEE ALSO
|
|
|
|
|
|
|
|
L<DBI>
|
|
|
|
|
|
|
|
=cut
|