mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-06 09:05:45 +00:00
00b6b15a1b
r=lpsolit a=justdave
1563 lines
53 KiB
Perl
1563 lines
53 KiB
Perl
# -*- 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>
|
|
# Tomas Kopal <Tomas.Kopal@altap.cz>
|
|
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
|
# Lance Larsh <lance.larsh@oracle.com>
|
|
|
|
package Bugzilla::DB;
|
|
|
|
use strict;
|
|
|
|
use DBI;
|
|
|
|
# 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);
|
|
|
|
%Bugzilla::DB::EXPORT_TAGS =
|
|
(
|
|
deprecated => [qw(SendSQL SqlQuote
|
|
MoreSQLData FetchSQLData FetchOneColumn
|
|
PushGlobalSQLState PopGlobalSQLState)
|
|
],
|
|
);
|
|
Exporter::export_ok_tags('deprecated');
|
|
|
|
use Bugzilla::Config qw(:DEFAULT :db);
|
|
use Bugzilla::Util;
|
|
use Bugzilla::Error;
|
|
use Bugzilla::DB::Schema;
|
|
|
|
#####################################################################
|
|
# Constants
|
|
#####################################################################
|
|
|
|
use constant BLOB_TYPE => DBI::SQL_BLOB;
|
|
|
|
#####################################################################
|
|
# Deprecated Functions
|
|
#####################################################################
|
|
|
|
# All this code is backwards compat fu. As such, its a bit ugly. Note the
|
|
# circular dependencies on Bugzilla.pm
|
|
# 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
|
|
# dependency
|
|
|
|
# XXX - mod_perl
|
|
# 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 = ();
|
|
|
|
my $_fetchahead;
|
|
|
|
sub SendSQL {
|
|
my ($str) = @_;
|
|
|
|
$_current_sth = Bugzilla->dbh->prepare($str);
|
|
|
|
$_current_sth->execute;
|
|
|
|
# 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;
|
|
|
|
undef $_fetchahead;
|
|
}
|
|
|
|
# Its much much better to use bound params instead of this
|
|
sub SqlQuote {
|
|
my ($str) = @_;
|
|
|
|
# Backwards compat code
|
|
return "''" if not defined $str;
|
|
|
|
my $res = Bugzilla->dbh->quote($str);
|
|
|
|
trick_taint($res);
|
|
|
|
return $res;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
return $_current_sth->fetchrow_array;
|
|
}
|
|
|
|
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
|
|
|
|
#####################################################################
|
|
# Connection Methods
|
|
#####################################################################
|
|
|
|
sub connect_shadow {
|
|
die "Tried to connect to non-existent shadowdb" unless Param('shadowdb');
|
|
|
|
return _connect($db_driver, Param("shadowdbhost"),
|
|
Param('shadowdb'), Param("shadowdbport"),
|
|
Param("shadowdbsock"), $db_user, $db_pass);
|
|
}
|
|
|
|
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,
|
|
$db_sock, $db_user, $db_pass);
|
|
}
|
|
|
|
sub _connect {
|
|
my ($driver, $host, $dbname, $port, $sock, $user, $pass) = @_;
|
|
|
|
# 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
|
|
eval ("require $pkg_module")
|
|
|| die ("'$db_module' is not a valid choice for \$db_driver in "
|
|
. " localconfig: " . $@);
|
|
|
|
# instantiate the correct DB specific module
|
|
my $dbh = $pkg_module->new($user, $pass, $host, $dbname, $port, $sock);
|
|
|
|
return $dbh;
|
|
}
|
|
|
|
sub _handle_error {
|
|
require Carp;
|
|
|
|
# Cut down the error string to a reasonable size
|
|
$_[0] = substr($_[0], 0, 2000) . ' ... ' . substr($_[0], -2000)
|
|
if length($_[0]) > 4000;
|
|
$_[0] = Carp::longmess($_[0]);
|
|
return 0; # Now let DBI handle raising the error
|
|
}
|
|
|
|
# List of abstract methods we are checking the derived class implements
|
|
our @_abstract_methods = qw(REQUIRED_VERSION PROGRAM_NAME DBD_VERSION
|
|
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;
|
|
}
|
|
|
|
sub sql_istrcmp {
|
|
my ($self, $left, $right, $op) = @_;
|
|
$op ||= "=";
|
|
|
|
return $self->sql_istring($left) . " $op " . $self->sql_istring($right);
|
|
}
|
|
|
|
sub sql_istring {
|
|
my ($self, $string) = @_;
|
|
|
|
return "LOWER($string)";
|
|
}
|
|
|
|
sub sql_position {
|
|
my ($self, $fragment, $text) = @_;
|
|
|
|
return "POSITION($fragment IN $text)";
|
|
}
|
|
|
|
sub sql_group_by {
|
|
my ($self, $needed_columns, $optional_columns) = @_;
|
|
|
|
my $expression = "GROUP BY $needed_columns";
|
|
$expression .= ", " . $optional_columns if defined($optional_columns);
|
|
|
|
return $expression;
|
|
}
|
|
|
|
sub sql_string_concat {
|
|
my ($self, @params) = @_;
|
|
|
|
return '(' . join(' || ', @params) . ')';
|
|
}
|
|
|
|
sub sql_fulltext_search {
|
|
my ($self, $column, $text) = @_;
|
|
|
|
# This is as close as we can get to doing full text search using
|
|
# standard ANSI SQL, without real full text search support. DB specific
|
|
# modules should override this, as this will be always much slower.
|
|
|
|
# make the string lowercase to do case insensitive search
|
|
my $lower_text = lc($text);
|
|
|
|
# split the text we search for into separate words
|
|
my @words = split(/\s+/, $lower_text);
|
|
|
|
# surround the words with wildcards and SQL quotes so we can use them
|
|
# in LIKE search clauses
|
|
@words = map($self->quote("%$_%"), @words);
|
|
|
|
# untaint words, since they are safe to use now that we've quoted them
|
|
map(trick_taint($_), @words);
|
|
|
|
# turn the words into a set of LIKE search clauses
|
|
@words = map("LOWER($column) LIKE $_", @words);
|
|
|
|
# search for occurrences of all specified words in the column
|
|
return "CASE WHEN (" . join(" AND ", @words) . ") THEN 1 ELSE 0 END";
|
|
}
|
|
|
|
#####################################################################
|
|
# General Info Methods
|
|
#####################################################################
|
|
|
|
# XXX - Needs to be documented.
|
|
sub bz_server_version {
|
|
my ($self) = @_;
|
|
return $self->get_info(18); # SQL_DBMS_VER
|
|
}
|
|
|
|
sub bz_last_key {
|
|
my ($self, $table, $column) = @_;
|
|
|
|
return $self->last_insert_id($db_name, undef, $table, $column);
|
|
}
|
|
|
|
sub bz_get_field_defs {
|
|
my ($self) = @_;
|
|
|
|
my $extra = "";
|
|
if (!Bugzilla->user->in_group(Param('timetrackinggroup'))) {
|
|
$extra = "AND name NOT IN ('estimated_time', 'remaining_time', " .
|
|
"'work_time', 'percentage_complete', 'deadline')";
|
|
}
|
|
|
|
my @fields;
|
|
my $sth = $self->prepare("SELECT name, description FROM fielddefs
|
|
WHERE obsolete = 0 $extra
|
|
ORDER BY sortkey");
|
|
$sth->execute();
|
|
while (my $field_ref = $sth->fetchrow_hashref()) {
|
|
push(@fields, $field_ref);
|
|
}
|
|
return(@fields);
|
|
}
|
|
|
|
#####################################################################
|
|
# Database Setup
|
|
#####################################################################
|
|
|
|
sub bz_setup_database {
|
|
my ($self) = @_;
|
|
|
|
# If we haven't ever stored a serialized schema,
|
|
# set up the bz_schema table and store it.
|
|
$self->_bz_init_schema_storage();
|
|
|
|
my @desired_tables = $self->_bz_schema->get_table_list();
|
|
|
|
foreach my $table_name (@desired_tables) {
|
|
$self->bz_add_table($table_name);
|
|
}
|
|
}
|
|
|
|
# The defauly implementation just returns what you passed-in. This function
|
|
# really exists just to be overridden in Bugzilla::DB::Mysql.
|
|
sub bz_enum_initial_values {
|
|
my ($self, $enum_defaults) = @_;
|
|
return $enum_defaults;
|
|
}
|
|
|
|
#####################################################################
|
|
# Schema Modification Methods
|
|
#####################################################################
|
|
|
|
sub bz_add_column {
|
|
my ($self, $table, $name, $new_def, $init_value) = @_;
|
|
|
|
# You can't add a NOT NULL column to a table with
|
|
# no DEFAULT statement, unless you have an init_value.
|
|
# SERIAL types are an exception, though, because they can
|
|
# auto-populate.
|
|
if ( $new_def->{NOTNULL} && !exists $new_def->{DEFAULT}
|
|
&& !defined $init_value && $new_def->{TYPE} !~ /SERIAL/)
|
|
{
|
|
die "Failed adding the column ${table}.${name}:\n You cannot add"
|
|
. " a NOT NULL column with no default to an existing table,\n"
|
|
. " unless you specify something for \$init_value."
|
|
}
|
|
|
|
my $current_def = $self->bz_column_info($table, $name);
|
|
|
|
if (!$current_def) {
|
|
my @statements = $self->_bz_real_schema->get_add_column_ddl(
|
|
$table, $name, $new_def,
|
|
defined $init_value ? $self->quote($init_value) : undef);
|
|
print "Adding new column $name to table $table ...\n";
|
|
foreach my $sql (@statements) {
|
|
$self->do($sql);
|
|
}
|
|
$self->_bz_real_schema->set_column($table, $name, $new_def);
|
|
$self->_bz_store_real_schema;
|
|
}
|
|
}
|
|
|
|
sub bz_alter_column {
|
|
my ($self, $table, $name, $new_def, $set_nulls_to) = @_;
|
|
|
|
my $current_def = $self->bz_column_info($table, $name);
|
|
|
|
if (!$self->_bz_schema->columns_equal($current_def, $new_def)) {
|
|
# You can't change a column to be NOT NULL if you have no DEFAULT
|
|
# and no value for $set_nulls_to, if there are any NULL values
|
|
# in that column.
|
|
if ($new_def->{NOTNULL} &&
|
|
!exists $new_def->{DEFAULT} && !defined $set_nulls_to)
|
|
{
|
|
# Check for NULLs
|
|
my $any_nulls = $self->selectrow_array(
|
|
"SELECT 1 FROM $table WHERE $name IS NULL");
|
|
if ($any_nulls) {
|
|
die "You cannot alter the ${table}.${name} column to be"
|
|
. " NOT NULL without\nspecifying a default or"
|
|
. " something for \$set_nulls_to, because"
|
|
. " there are\nNULL values currently in it.";
|
|
}
|
|
}
|
|
$self->bz_alter_column_raw($table, $name, $new_def, $current_def,
|
|
$set_nulls_to);
|
|
$self->_bz_real_schema->set_column($table, $name, $new_def);
|
|
$self->_bz_store_real_schema;
|
|
}
|
|
}
|
|
|
|
|
|
# bz_alter_column_raw($table, $name, $new_def, $current_def)
|
|
#
|
|
# Description: A helper function for bz_alter_column.
|
|
# Alters a column in the database
|
|
# without updating any Schema object. Generally
|
|
# should only be called by bz_alter_column.
|
|
# Used when either: (1) You don't yet have a Schema
|
|
# object but you need to alter a column, for some reason.
|
|
# (2) You need to alter a column for some database-specific
|
|
# reason.
|
|
# Params: $table - The name of the table the column is on.
|
|
# $name - The name of the column you're changing.
|
|
# $new_def - The abstract definition that you are changing
|
|
# this column to.
|
|
# $current_def - (optional) The current definition of the
|
|
# column. Will be used in the output message,
|
|
# if given.
|
|
# $set_nulls_to - The same as the param of the same name
|
|
# from bz_alter_column.
|
|
# Returns: nothing
|
|
#
|
|
sub bz_alter_column_raw {
|
|
my ($self, $table, $name, $new_def, $current_def, $set_nulls_to) = @_;
|
|
my @statements = $self->_bz_real_schema->get_alter_column_ddl(
|
|
$table, $name, $new_def,
|
|
defined $set_nulls_to ? $self->quote($set_nulls_to) : undef);
|
|
my $new_ddl = $self->_bz_schema->get_type_ddl($new_def);
|
|
print "Updating column $name in table $table ...\n";
|
|
if (defined $current_def) {
|
|
my $old_ddl = $self->_bz_schema->get_type_ddl($current_def);
|
|
print "Old: $old_ddl\n";
|
|
}
|
|
print "New: $new_ddl\n";
|
|
$self->do($_) foreach (@statements);
|
|
}
|
|
|
|
sub bz_add_index {
|
|
my ($self, $table, $name, $definition) = @_;
|
|
|
|
my $index_exists = $self->bz_index_info($table, $name);
|
|
|
|
if (!$index_exists) {
|
|
$self->bz_add_index_raw($table, $name, $definition);
|
|
$self->_bz_real_schema->set_index($table, $name, $definition);
|
|
$self->_bz_store_real_schema;
|
|
}
|
|
}
|
|
|
|
# bz_add_index_raw($table, $name, $silent)
|
|
#
|
|
# Description: A helper function for bz_add_index.
|
|
# Adds an index to the database
|
|
# without updating any Schema object. Generally
|
|
# should only be called by bz_add_index.
|
|
# Used when you don't yet have a Schema
|
|
# object but you need to add an index, for some reason.
|
|
# Params: $table - The name of the table the index is on.
|
|
# $name - The name of the index you're adding.
|
|
# $definition - The abstract index definition, in hashref
|
|
# or arrayref format.
|
|
# $silent - (optional) If specified and true, don't output
|
|
# any message about this change.
|
|
# Returns: nothing
|
|
#
|
|
sub bz_add_index_raw {
|
|
my ($self, $table, $name, $definition, $silent) = @_;
|
|
my @statements = $self->_bz_schema->get_add_index_ddl(
|
|
$table, $name, $definition);
|
|
print "Adding new index '$name' to the $table table ...\n" unless $silent;
|
|
$self->do($_) foreach (@statements);
|
|
}
|
|
|
|
sub bz_add_table {
|
|
my ($self, $name) = @_;
|
|
|
|
my $table_exists = $self->bz_table_info($name);
|
|
|
|
if (!$table_exists) {
|
|
$self->_bz_add_table_raw($name);
|
|
$self->_bz_real_schema->add_table($name,
|
|
$self->_bz_schema->get_table_abstract($name));
|
|
$self->_bz_store_real_schema;
|
|
}
|
|
}
|
|
|
|
# _bz_add_table_raw($name) - Private
|
|
#
|
|
# Description: A helper function for bz_add_table.
|
|
# Creates a table in the database without
|
|
# updating any Schema object. Generally
|
|
# should only be called by bz_add_table and by
|
|
# _bz_init_schema_storage. Used when you don't
|
|
# yet have a Schema object but you need to
|
|
# add a table, for some reason.
|
|
# Params: $name - The name of the table you're creating.
|
|
# The definition for the table is pulled from
|
|
# _bz_schema.
|
|
# Returns: nothing
|
|
#
|
|
sub _bz_add_table_raw {
|
|
my ($self, $name) = @_;
|
|
my @statements = $self->_bz_schema->get_table_ddl($name);
|
|
print "Adding new table $name ...\n";
|
|
$self->do($_) foreach (@statements);
|
|
}
|
|
|
|
sub bz_drop_column {
|
|
my ($self, $table, $column) = @_;
|
|
|
|
my $current_def = $self->bz_column_info($table, $column);
|
|
|
|
if ($current_def) {
|
|
my @statements = $self->_bz_real_schema->get_drop_column_ddl(
|
|
$table, $column);
|
|
print "Deleting unused column $column from table $table ...\n";
|
|
foreach my $sql (@statements) {
|
|
# Because this is a deletion, we don't want to die hard if
|
|
# we fail because of some local customization. If something
|
|
# is already gone, that's fine with us!
|
|
eval { $self->do($sql); } or warn "Failed SQL: [$sql] Error: $@";
|
|
}
|
|
$self->_bz_real_schema->delete_column($table, $column);
|
|
$self->_bz_store_real_schema;
|
|
}
|
|
}
|
|
|
|
sub bz_drop_index {
|
|
my ($self, $table, $name) = @_;
|
|
|
|
my $index_exists = $self->bz_index_info($table, $name);
|
|
|
|
if ($index_exists) {
|
|
$self->bz_drop_index_raw($table, $name);
|
|
$self->_bz_real_schema->delete_index($table, $name);
|
|
$self->_bz_store_real_schema;
|
|
}
|
|
}
|
|
|
|
# bz_drop_index_raw($table, $name, $silent)
|
|
#
|
|
# Description: A helper function for bz_drop_index.
|
|
# Drops an index from the database
|
|
# without updating any Schema object. Generally
|
|
# should only be called by bz_drop_index.
|
|
# Used when either: (1) You don't yet have a Schema
|
|
# object but you need to drop an index, for some reason.
|
|
# (2) You need to drop an index that somehow got into the
|
|
# database but doesn't exist in Schema.
|
|
# Params: $table - The name of the table the index is on.
|
|
# $name - The name of the index you're dropping.
|
|
# $silent - (optional) If specified and true, don't output
|
|
# any message about this change.
|
|
# Returns: nothing
|
|
#
|
|
sub bz_drop_index_raw {
|
|
my ($self, $table, $name, $silent) = @_;
|
|
my @statements = $self->_bz_schema->get_drop_index_ddl(
|
|
$table, $name);
|
|
print "Removing index '$name' from the $table table...\n" unless $silent;
|
|
foreach my $sql (@statements) {
|
|
# Because this is a deletion, we don't want to die hard if
|
|
# we fail because of some local customization. If something
|
|
# is already gone, that's fine with us!
|
|
eval { $self->do($sql) } or warn "Failed SQL: [$sql] Error: $@";
|
|
}
|
|
}
|
|
|
|
sub bz_drop_table {
|
|
my ($self, $name) = @_;
|
|
|
|
my $table_exists = $self->bz_table_info($name);
|
|
|
|
if ($table_exists) {
|
|
my @statements = $self->_bz_schema->get_drop_table_ddl($name);
|
|
print "Dropping table $name...\n";
|
|
foreach my $sql (@statements) {
|
|
# Because this is a deletion, we don't want to die hard if
|
|
# we fail because of some local customization. If something
|
|
# is already gone, that's fine with us!
|
|
eval { $self->do($sql); } or warn "Failed SQL: [$sql] Error: $@";
|
|
}
|
|
$self->_bz_real_schema->delete_table($name);
|
|
$self->_bz_store_real_schema;
|
|
}
|
|
}
|
|
|
|
sub bz_rename_column {
|
|
my ($self, $table, $old_name, $new_name) = @_;
|
|
|
|
my $old_col_exists = $self->bz_column_info($table, $old_name);
|
|
|
|
if ($old_col_exists) {
|
|
my $already_renamed = $self->bz_column_info($table, $new_name);
|
|
die "Name conflict: Cannot rename ${table}.${old_name} to"
|
|
. " ${table}.${new_name},\nbecause ${table}.${new_name}"
|
|
. " already exists." if $already_renamed;
|
|
my @statements = $self->_bz_real_schema->get_rename_column_ddl(
|
|
$table, $old_name, $new_name);
|
|
print "Changing column $old_name in table $table to"
|
|
. " be named $new_name...\n";
|
|
foreach my $sql (@statements) {
|
|
$self->do($sql);
|
|
}
|
|
$self->_bz_real_schema->rename_column($table, $old_name, $new_name);
|
|
$self->_bz_store_real_schema;
|
|
}
|
|
}
|
|
|
|
#####################################################################
|
|
# Schema Information Methods
|
|
#####################################################################
|
|
|
|
sub _bz_schema {
|
|
my ($self) = @_;
|
|
return $self->{private_bz_schema} if exists $self->{private_bz_schema};
|
|
$self->{private_bz_schema} =
|
|
Bugzilla::DB::Schema->new($self->MODULE_NAME);
|
|
return $self->{private_bz_schema};
|
|
}
|
|
|
|
# _bz_get_initial_schema()
|
|
#
|
|
# Description: A protected method, intended for use only by Bugzilla::DB
|
|
# and subclasses. Used to get the initial Schema that will
|
|
# be wirtten to disk for _bz_init_schema_storage. You probably
|
|
# want to use _bz_schema or _bz_real_schema instead of this
|
|
# method.
|
|
# Params: none
|
|
# Returns: A Schema object that can be serialized and written to disk
|
|
# for _bz_init_schema_storage.
|
|
sub _bz_get_initial_schema {
|
|
my ($self) = @_;
|
|
return $self->_bz_schema->get_empty_schema();
|
|
}
|
|
|
|
sub bz_column_info {
|
|
my ($self, $table, $column) = @_;
|
|
return $self->_bz_real_schema->get_column_abstract($table, $column);
|
|
}
|
|
|
|
sub bz_index_info {
|
|
my ($self, $table, $index) = @_;
|
|
my $index_def =
|
|
$self->_bz_real_schema->get_index_abstract($table, $index);
|
|
if (ref($index_def) eq 'ARRAY') {
|
|
$index_def = {FIELDS => $index_def, TYPE => ''};
|
|
}
|
|
return $index_def;
|
|
}
|
|
|
|
sub bz_table_info {
|
|
my ($self, $table) = @_;
|
|
return $self->_bz_real_schema->get_table_abstract($table);
|
|
}
|
|
|
|
|
|
sub bz_table_columns {
|
|
my ($self, $table) = @_;
|
|
return $self->_bz_real_schema->get_table_columns($table);
|
|
}
|
|
|
|
#####################################################################
|
|
# Protected "Real Database" Schema Information Methods
|
|
#####################################################################
|
|
|
|
# Only Bugzilla::DB and subclasses should use these methods.
|
|
# If you need a method that does the same thing as one of these
|
|
# methods, use the version without _real on the end.
|
|
|
|
# bz_table_columns_real($table)
|
|
#
|
|
# Description: Returns a list of columns on a given table
|
|
# as the table actually is, on the disk.
|
|
# Params: $table - Name of the table.
|
|
# Returns: An array of column names.
|
|
#
|
|
sub bz_table_columns_real {
|
|
my ($self, $table) = @_;
|
|
my $sth = $self->column_info(undef, undef, $table, '%');
|
|
return @{ $self->selectcol_arrayref($sth, {Columns => [4]}) };
|
|
}
|
|
|
|
# bz_table_list_real()
|
|
#
|
|
# Description: Gets a list of tables in the current
|
|
# database, directly from the disk.
|
|
# Params: none
|
|
# Returns: An array containing table names.
|
|
sub bz_table_list_real {
|
|
my ($self) = @_;
|
|
my $table_sth = $self->table_info(undef, undef, undef, "TABLE");
|
|
return @{$self->selectcol_arrayref($table_sth, { Columns => [3] })};
|
|
}
|
|
|
|
#####################################################################
|
|
# Transaction Methods
|
|
#####################################################################
|
|
|
|
sub bz_start_transaction {
|
|
my ($self) = @_;
|
|
|
|
if ($self->{private_bz_in_transaction}) {
|
|
ThrowCodeError("nested_transaction");
|
|
} else {
|
|
# Turn AutoCommit off and start a new transaction
|
|
$self->begin_work();
|
|
$self->{private_bz_in_transaction} = 1;
|
|
}
|
|
}
|
|
|
|
sub bz_commit_transaction {
|
|
my ($self) = @_;
|
|
|
|
if (!$self->{private_bz_in_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}) {
|
|
ThrowCodeError("not_in_transaction");
|
|
} else {
|
|
$self->rollback();
|
|
|
|
$self->{private_bz_in_transaction} = 0;
|
|
}
|
|
}
|
|
|
|
#####################################################################
|
|
# Subclass Helpers
|
|
#####################################################################
|
|
|
|
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)
|
|
$attributes = { RaiseError => 0,
|
|
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
|
|
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;
|
|
|
|
# class variables
|
|
$self->{private_bz_in_transaction} = 0;
|
|
|
|
bless ($self, $class);
|
|
|
|
return $self;
|
|
}
|
|
|
|
#####################################################################
|
|
# Private Methods
|
|
#####################################################################
|
|
|
|
=begin private
|
|
|
|
=head1 PRIVATE METHODS
|
|
|
|
These methods really are private. Do not override them in subclasses.
|
|
|
|
=over 4
|
|
|
|
=item C<_init_bz_schema_storage>
|
|
|
|
Description: Initializes the bz_schema table if it contains nothing.
|
|
Params: none
|
|
Returns: nothing
|
|
|
|
=cut
|
|
|
|
sub _bz_init_schema_storage {
|
|
my ($self) = @_;
|
|
|
|
my $table_size;
|
|
eval {
|
|
$table_size =
|
|
$self->selectrow_array("SELECT COUNT(*) FROM bz_schema");
|
|
};
|
|
|
|
if (!$table_size) {
|
|
my $init_schema = $self->_bz_get_initial_schema;
|
|
my $store_me = $init_schema->serialize_abstract();
|
|
my $schema_version = $init_schema->SCHEMA_VERSION;
|
|
|
|
# If table_size is not defined, then we hit an error reading the
|
|
# bz_schema table, which means it probably doesn't exist yet. So,
|
|
# we have to create it. If we failed above for some other reason,
|
|
# we'll see the failure here.
|
|
# However, we must create the table after we do get_initial_schema,
|
|
# because some versions of get_initial_schema read that the table
|
|
# exists and then add it to the Schema, where other versions don't.
|
|
if (!defined $table_size) {
|
|
$self->_bz_add_table_raw('bz_schema');
|
|
}
|
|
|
|
print "Initializing the new Schema storage...\n";
|
|
my $sth = $self->prepare("INSERT INTO bz_schema "
|
|
." (schema_data, version) VALUES (?,?)");
|
|
$sth->bind_param(1, $store_me, $self->BLOB_TYPE);
|
|
$sth->bind_param(2, $schema_version);
|
|
$sth->execute();
|
|
|
|
# And now we have to update the on-disk schema to hold the bz_schema
|
|
# table, if the bz_schema table didn't exist when we were called.
|
|
if (!defined $table_size) {
|
|
$self->_bz_real_schema->add_table('bz_schema',
|
|
$self->_bz_schema->get_table_abstract('bz_schema'));
|
|
$self->_bz_store_real_schema;
|
|
}
|
|
}
|
|
# Sanity check
|
|
elsif ($table_size > 1) {
|
|
# We tell them to delete the newer one. Better to have checksetup
|
|
# run migration code too many times than to have it not run the
|
|
# correct migration code at all.
|
|
die "Attempted to initialize the schema but there are already "
|
|
. " $table_size copies of it stored.\nThis should never happen.\n"
|
|
. " Compare the rows of the bz_schema table and delete the "
|
|
. "newer one(s).";
|
|
}
|
|
}
|
|
|
|
=item C<_bz_real_schema()>
|
|
|
|
Description: Returns a Schema object representing the database
|
|
that is being used in the current installation.
|
|
Params: none
|
|
Returns: A C<Bugzilla::DB::Schema> object representing the database
|
|
as it exists on the disk.
|
|
|
|
=cut
|
|
|
|
sub _bz_real_schema {
|
|
my ($self) = @_;
|
|
return $self->{private_real_schema} if exists $self->{private_real_schema};
|
|
|
|
my ($data, $version) = $self->selectrow_array(
|
|
"SELECT schema_data, version FROM bz_schema");
|
|
|
|
(die "_bz_real_schema tried to read the bz_schema table but it's empty!")
|
|
if !$data;
|
|
|
|
$self->{private_real_schema} =
|
|
$self->_bz_schema->deserialize_abstract($data, $version);
|
|
|
|
return $self->{private_real_schema};
|
|
}
|
|
|
|
=item C<_bz_store_real_schema()>
|
|
|
|
Description: Stores the _bz_real_schema structures in the database
|
|
for later recovery. Call this function whenever you make
|
|
a change to the _bz_real_schema.
|
|
Params: none
|
|
Returns: nothing
|
|
|
|
Precondition: $self->{_bz_real_schema} must exist.
|
|
|
|
=back
|
|
|
|
=end private
|
|
|
|
=cut
|
|
|
|
sub _bz_store_real_schema {
|
|
my ($self) = @_;
|
|
|
|
# Make sure that there's a schema to update
|
|
my $table_size = $self->selectrow_array("SELECT COUNT(*) FROM bz_schema");
|
|
|
|
die "Attempted to update the bz_schema table but there's nothing "
|
|
. "there to update. Run checksetup." unless $table_size;
|
|
|
|
# We want to store the current object, not one
|
|
# that we read from the database. So we use the actual hash
|
|
# member instead of the subroutine call. If the hash
|
|
# member is not defined, we will (and should) fail.
|
|
my $update_schema = $self->{private_real_schema};
|
|
my $store_me = $update_schema->serialize_abstract();
|
|
my $schema_version = $update_schema->SCHEMA_VERSION;
|
|
my $sth = $self->prepare("UPDATE bz_schema
|
|
SET schema_data = ?, version = ?");
|
|
$sth->bind_param(1, $store_me, $self->BLOB_TYPE);
|
|
$sth->bind_param(2, $schema_version);
|
|
$sth->execute();
|
|
}
|
|
|
|
1;
|
|
|
|
__END__
|
|
|
|
=head1 NAME
|
|
|
|
Bugzilla::DB - Database access routines, using L<DBI>
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
# Obtain db handle
|
|
use Bugzilla::DB;
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
# 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;
|
|
|
|
# Schema Modification
|
|
$dbh->bz_add_column($table, $name, \%definition, $init_value);
|
|
$dbh->bz_add_index($table, $name, $definition);
|
|
$dbh->bz_add_table($name);
|
|
$dbh->bz_drop_index($table, $name);
|
|
$dbh->bz_drop_table($name);
|
|
$dbh->bz_alter_column($table, $name, \%new_def, $set_nulls_to);
|
|
$dbh->bz_drop_column($table, $column);
|
|
$dbh->bz_rename_column($table, $old_name, $new_name);
|
|
|
|
# Schema Information
|
|
my $column = $dbh->bz_column_info($table, $column);
|
|
my $index = $dbh->bz_index_info($table, $index);
|
|
|
|
# General Information
|
|
my @fields = $dbh->bz_get_field_defs();
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
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.
|
|
|
|
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 CONSTANTS
|
|
|
|
Subclasses of Bugzilla::DB are required to define certain constants. These
|
|
constants are required to be subroutines or "use constant" variables.
|
|
|
|
=over 4
|
|
|
|
=item C<BLOB_TYPE>
|
|
|
|
The C<\%attr> argument that must be passed to bind_param in order to
|
|
correctly escape a C<LONGBLOB> type.
|
|
|
|
=item C<REQUIRED_VERSION>
|
|
|
|
This is the minimum required version of the database server that the
|
|
Bugzilla::DB subclass requires. It should be in a format suitable for
|
|
passing to vers_cmp during installation.
|
|
|
|
=item C<PROGRAM_NAME>
|
|
|
|
The human-readable name of this database. For example, for MySQL, this
|
|
would be 'MySQL.' You should not depend on this variable to know what
|
|
type of database you are running on; this is intended merely for displaying
|
|
to the admin to let them know what DB they're running.
|
|
|
|
=item C<MODULE_NAME>
|
|
|
|
The name of the Bugzilla::DB module that we are. For example, for the MySQL
|
|
Bugzilla::DB module, this would be "Mysql." For PostgreSQL it would be "Pg."
|
|
|
|
=item C<DBD_VERSION>
|
|
|
|
The minimum version of the DBD module that we require for this database.
|
|
|
|
=back
|
|
|
|
|
|
=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.
|
|
|
|
=head2 Functions
|
|
|
|
=over 4
|
|
|
|
=item C<connect_main>
|
|
|
|
Description: Function to connect to the main database, returning a new
|
|
database handle.
|
|
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.
|
|
Returns: new instance of the DB class
|
|
|
|
=item C<connect_shadow>
|
|
|
|
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.
|
|
|
|
=back
|
|
|
|
|
|
=head1 ABSTRACT METHODS
|
|
|
|
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_>.
|
|
|
|
=over 4
|
|
|
|
=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 (case insensitive) in format suitable for a given
|
|
database.
|
|
Abstract method, should be overriden by database specific code.
|
|
Params: $expr = SQL expression for the text to be searched (scalar)
|
|
$pattern = the regular expression to search for (scalar)
|
|
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 (case insensitive) in format suitable for a given
|
|
database.
|
|
Abstract method, should be overriden by database specific code.
|
|
Params: $expr = SQL expression for the text to be searched (scalar)
|
|
$pattern = the regular expression to search for (scalar)
|
|
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_from_days>
|
|
|
|
Description: Outputs SQL syntax for converting Julian days to date.
|
|
Abstract method, should be overriden by database specific code.
|
|
Params: $days = days to convert to date
|
|
Returns: formatted SQL for returning Julian days in dates. (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') (integer)
|
|
$units - the units the interval is in (e.g. 'MINUTE') (string)
|
|
Returns: formatted SQL for interval function (scalar)
|
|
|
|
=item C<sql_position>
|
|
|
|
Description: Outputs proper SQL syntax determinig position of a substring
|
|
(fragment) withing a string (text). Note: if the substring or
|
|
text are string constants, they must be properly quoted
|
|
(e.g. "'pattern'").
|
|
Params: $fragment = the string fragment we are searching for (scalar)
|
|
$text = the text to search (scalar)
|
|
Returns: formatted SQL for substring search (scalar)
|
|
|
|
=item C<sql_group_by>
|
|
|
|
Description: Outputs proper SQL syntax for grouping the result of a query.
|
|
For ANSI SQL databases, we need to group by all columns we are
|
|
querying for (except for columns used in aggregate functions).
|
|
Some databases require (or even allow) to specify only one
|
|
or few columns if the result is uniquely defined. For those
|
|
databases, the default implementation needs to be overloaded.
|
|
Params: $needed_columns = string with comma separated list of columns
|
|
we need to group by to get expected result (scalar)
|
|
$optional_columns = string with comma separated list of all
|
|
other columns we are querying for, but which are not in the
|
|
required list.
|
|
Returns: formatted SQL for row grouping (scalar)
|
|
|
|
=item C<sql_string_concat>
|
|
|
|
Description: Returns SQL syntax for concatenating multiple strings (constants
|
|
or values from table columns) together.
|
|
Params: @params = array of column names or strings to concatenate
|
|
Returns: formatted SQL for concatenating specified strings
|
|
|
|
=item C<sql_fulltext_search>
|
|
|
|
Description: Returns SQL syntax for performing a full text search for
|
|
specified text on a given column.
|
|
There is a ANSI SQL version of this method implemented using
|
|
LIKE operator, but it's not a real full text search. DB specific
|
|
modules should override this, as this generic implementation will
|
|
be always much slower. This generic implementation returns
|
|
'relevance' as 0 for no match, or 1 for a match.
|
|
Params: $column = name of column to search (scalar)
|
|
$text = text to search for (scalar)
|
|
Returns: formatted SQL for full text search
|
|
|
|
=item C<sql_istrcmp>
|
|
|
|
Description: Returns SQL for a case-insensitive string comparison.
|
|
Params: $left - What should be on the left-hand-side of the
|
|
operation.
|
|
$right - What should be on the right-hand-side of the
|
|
operation.
|
|
$op (optional) - What the operation is. Should be a
|
|
valid ANSI SQL comparison operator, like "=", "<",
|
|
"LIKE", etc. Defaults to "=" if not specified.
|
|
Returns: A SQL statement that will run the comparison in
|
|
a case-insensitive fashion.
|
|
Note: Uses sql_istring, so it has the same performance concerns.
|
|
Try to avoid using this function unless absolutely necessary.
|
|
Subclass Implementors: Override sql_istring instead of this
|
|
function, most of the time (this function uses sql_istring).
|
|
|
|
=item C<sql_istring>
|
|
|
|
Description: Returns SQL syntax "preparing" a string or text column for
|
|
case-insensitive comparison.
|
|
Params: $string - string to convert (scalar)
|
|
Returns: formatted SQL making the string case insensitive
|
|
Note: The default implementation simply calls LOWER on the parameter.
|
|
If this is used to search on a text column with index, the index
|
|
will not be usually used unless it was created as LOWER(column).
|
|
|
|
=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.
|
|
Params: $abort = UNLOCK_ABORT (true, 1) if the operation on locked tables
|
|
failed (if transactions are supported, the action will be rolled
|
|
back). False (0) or no param if the operation succeeded.
|
|
Returns: none
|
|
|
|
=back
|
|
|
|
|
|
=head1 IMPLEMENTED METHODS
|
|
|
|
These methods are implemented in Bugzilla::DB, and only need
|
|
to be implemented in subclasses if you need to override them for
|
|
database-compatibility reasons.
|
|
|
|
=head2 General Information Methods
|
|
|
|
These methods return information about data in the database.
|
|
|
|
=over 4
|
|
|
|
=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)
|
|
|
|
=back
|
|
|
|
|
|
=head2 Database Setup Methods
|
|
|
|
These methods are used by the Bugzilla installation programs to set up
|
|
the database.
|
|
|
|
=over 4
|
|
|
|
=item C<bz_enum_initial_values(\%enum_defaults)>
|
|
|
|
Description: For an upgrade or an initial installation, provides
|
|
what the values should be for the "enum"-type fields,
|
|
such as version, op_sys, rep_platform, etc.
|
|
Params: \%enum_defaults - The default initial list of values for
|
|
each enum field. A hash, with the field
|
|
names pointing to an arrayref of values.
|
|
Returns: A hashref with the correct initial values for the enum fields.
|
|
|
|
=back
|
|
|
|
|
|
=head2 Schema Modification Methods
|
|
|
|
These methods modify the current Bugzilla Schema.
|
|
|
|
Where a parameter says "Abstract index/column definition", it returns/takes
|
|
information in the formats defined for indexes and columns in
|
|
C<Bugzilla::DB::Schema::ABSTRACT_SCHEMA>.
|
|
|
|
=over 4
|
|
|
|
=item C<bz_add_column($table, $name, \%definition, $init_value)>
|
|
|
|
Description: Adds a new column to a table in the database. Prints out
|
|
a brief statement that it did so, to stdout.
|
|
Note that you cannot add a NOT NULL column that has no
|
|
default -- the database won't know what to set all
|
|
the NOT NULL values to.
|
|
Params: $table = the table where the column is being added
|
|
$name = the name of the new column
|
|
\%definition = Abstract column definition for the new column
|
|
$init_value = (optional) An initial value to set the column
|
|
to. Required if your column is NOT NULL and has
|
|
no DEFAULT set.
|
|
Returns: nothing
|
|
|
|
=item C<bz_add_index($table, $name, $definition)>
|
|
|
|
Description: Adds a new index to a table in the database. Prints
|
|
out a brief statement that it did so, to stdout.
|
|
If the index already exists, we will do nothing.
|
|
Params: $table - The table the new index is on.
|
|
$name - A name for the new index.
|
|
$definition - An abstract index definition.
|
|
Either a hashref or an arrayref.
|
|
Returns: nothing
|
|
|
|
=item C<bz_add_table($name)>
|
|
|
|
Description: Creates a new table in the database, based on the
|
|
definition for that table in the abstract schema.
|
|
Note that unlike the other 'add' functions, this does
|
|
not take a definition, but always creates the table
|
|
as it exists in the ABSTRACT_SCHEMA.
|
|
If a table with that name already exists, then this
|
|
function returns silently.
|
|
Params: $name - The name of the table you want to create.
|
|
Returns: nothing
|
|
|
|
=item C<bz_drop_index($table, $name)>
|
|
|
|
Description: Removes an index from the database. Prints out a brief
|
|
statement that it did so, to stdout. If the index
|
|
doesn't exist, we do nothing.
|
|
Params: $table - The table that the index is on.
|
|
$name - The name of the index that you want to drop.
|
|
Returns: nothing
|
|
|
|
=item C<bz_drop_table($name)>
|
|
|
|
Description: Drops a table from the database. If the table
|
|
doesn't exist, we just return silently.
|
|
Params: $name - The name of the table to drop.
|
|
Returns: nothing
|
|
|
|
=item C<bz_alter_column($table, $name, \%new_def, $set_nulls_to)>
|
|
|
|
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
|
|
$name = the name of the column you want to change
|
|
$new_def = An abstract column definition for the new
|
|
data type of the columm
|
|
$set_nulls_to = (Optional) If you are changing the column
|
|
to be NOT NULL, you probably also want to
|
|
set any existing NULL columns to a particular
|
|
value. Specify that value here.
|
|
NOTE: The value should not already be SQL-quoted.
|
|
Returns: nothing
|
|
|
|
=item C<bz_drop_column($table, $column)>
|
|
|
|
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_rename_column($table, $old_name, $new_name)>
|
|
|
|
Description: Renames a column in a database table. If the C<$old_name>
|
|
column doesn't exist, we return without doing anything.
|
|
If C<$old_name> and C<$new_name> both already exist in the
|
|
table specified, we fail.
|
|
Params: $table = The table containing the column
|
|
that you want to rename
|
|
$old_name = The current name of the column that
|
|
you want to rename
|
|
$new_name = The new name of the column
|
|
Returns: nothing
|
|
|
|
=back
|
|
|
|
|
|
=head2 Schema Information Methods
|
|
|
|
These methods return information about the current Bugzilla database
|
|
schema, as it currently exists on the disk.
|
|
|
|
Where a parameter says "Abstract index/column definition", it returns/takes
|
|
information in the formats defined for indexes and columns in
|
|
C<Bugzilla::DB::Schema::ABSTRACT_SCHEMA>.
|
|
|
|
=over 4
|
|
|
|
=item C<bz_column_info($table, $column)>
|
|
|
|
Description: Get abstract column definition.
|
|
Params: $table - The name of the table the column is in.
|
|
$column - The name of the column.
|
|
Returns: An abstract column definition for that column.
|
|
If the table or column does not exist, we return undef.
|
|
|
|
=item C<bz_index_info($table, $index)>
|
|
|
|
Description: Get abstract index definition.
|
|
Params: $table - The table the index is on.
|
|
$index - The name of the index.
|
|
Returns: An abstract index definition for that index,
|
|
always in hashref format. The hashref will
|
|
always contain the TYPE element, but it will
|
|
be an empty string if it's just a normal index.
|
|
If the index does not exist, we return undef.
|
|
|
|
=back
|
|
|
|
|
|
=head2 Deprecated Schema Information Methods
|
|
|
|
These methods return info about the current Bugzilla database, for
|
|
MySQL only.
|
|
|
|
=over 4
|
|
|
|
=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
|
|
|
|
=back
|
|
|
|
|
|
=head2 Transaction Methods
|
|
|
|
These methods deal with the starting and stopping of transactions
|
|
in the database.
|
|
|
|
=over 4
|
|
|
|
=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
|
|
|
|
=back
|
|
|
|
|
|
=head1 SUBCLASS HELPERS
|
|
|
|
Methods in this class are intended to be used by subclasses to help them
|
|
with their functions.
|
|
|
|
=over 4
|
|
|
|
=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.
|
|
|
|
=back
|
|
|
|
|
|
=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
|