summaryrefslogtreecommitdiffstats
path: root/Bugzilla/DB.pm
diff options
context:
space:
mode:
Diffstat (limited to 'Bugzilla/DB.pm')
-rw-r--r--Bugzilla/DB.pm1954
1 files changed, 1006 insertions, 948 deletions
diff --git a/Bugzilla/DB.pm b/Bugzilla/DB.pm
index 87110aaaa..1003c4673 100644
--- a/Bugzilla/DB.pm
+++ b/Bugzilla/DB.pm
@@ -13,10 +13,7 @@ use Moo;
use DBI;
use DBIx::Connector;
-has 'connector' => (
- is => 'lazy',
- handles => [ qw( dbh ) ],
-);
+has 'connector' => (is => 'lazy', handles => [qw( dbh )],);
use Bugzilla::Logging;
use Bugzilla::Constants;
@@ -36,39 +33,36 @@ use Storable qw(dclone);
use English qw(-no_match_vars);
use Module::Runtime qw(require_module);
-has [qw(dsn user pass attrs)] => (
- is => 'ro',
- required => 1,
-);
+has [qw(dsn user pass attrs)] => (is => 'ro', required => 1,);
# Install proxy methods to the DBI object.
# We can't use handles() as DBIx::Connector->dbh has to be called each
# time we need a DBI handle to ensure the connection is alive.
{
- my @DBI_METHODS = qw(
- begin_work column_info commit do errstr get_info last_insert_id ping prepare
- primary_key quote_identifier rollback selectall_arrayref selectall_hashref
- selectcol_arrayref selectrow_array selectrow_arrayref selectrow_hashref table_info
+ my @DBI_METHODS = qw(
+ begin_work column_info commit do errstr get_info last_insert_id ping prepare
+ primary_key quote_identifier rollback selectall_arrayref selectall_hashref
+ selectcol_arrayref selectrow_array selectrow_arrayref selectrow_hashref table_info
+ );
+ my $stash = Package::Stash->new(__PACKAGE__);
+
+ foreach my $method (@DBI_METHODS) {
+ my $symbol = '&' . $method;
+ $stash->add_symbol(
+ $symbol => sub {
+ my $self = shift;
+ return $self->dbh->$method(@_);
+ }
);
- my $stash = Package::Stash->new(__PACKAGE__);
-
- foreach my $method (@DBI_METHODS) {
- my $symbol = '&' . $method;
- $stash->add_symbol(
- $symbol => sub {
- my $self = shift;
- return $self->dbh->$method(@_);
- }
- );
- }
+ }
}
#####################################################################
# Constants
#####################################################################
-use constant BLOB_TYPE => DBI::SQL_BLOB;
+use constant BLOB_TYPE => DBI::SQL_BLOB;
use constant ISOLATION_LEVEL => 'REPEATABLE READ';
# Set default values for what used to be the enum types. These values
@@ -81,14 +75,14 @@ use constant ISOLATION_LEVEL => 'REPEATABLE READ';
# Bugzilla with enums. After that, they are either controlled through
# the Bugzilla UI or through the DB.
use constant ENUM_DEFAULTS => {
- bug_severity => ['blocker', 'critical', 'major', 'normal',
- 'minor', 'trivial', 'enhancement'],
- priority => ["Highest", "High", "Normal", "Low", "Lowest", "---"],
- op_sys => ["All","Windows","Mac OS","Linux","Other"],
- rep_platform => ["All","PC","Macintosh","Other"],
- bug_status => ["UNCONFIRMED","CONFIRMED","IN_PROGRESS","RESOLVED",
- "VERIFIED"],
- resolution => ["","FIXED","INVALID","WONTFIX", "DUPLICATE","WORKSFORME"],
+ bug_severity =>
+ ['blocker', 'critical', 'major', 'normal', 'minor', 'trivial', 'enhancement'],
+ priority => ["Highest", "High", "Normal", "Low", "Lowest", "---"],
+ op_sys => ["All", "Windows", "Mac OS", "Linux", "Other"],
+ rep_platform => ["All", "PC", "Macintosh", "Other"],
+ bug_status =>
+ ["UNCONFIRMED", "CONFIRMED", "IN_PROGRESS", "RESOLVED", "VERIFIED"],
+ resolution => ["", "FIXED", "INVALID", "WONTFIX", "DUPLICATE", "WORKSFORME"],
};
# The character that means "OR" in a boolean fulltext search. If empty,
@@ -122,10 +116,10 @@ use constant INDEX_DROPS_REQUIRE_FK_DROPS => 1;
#####################################################################
sub quote {
- my $self = shift;
- my $retval = $self->dbh->quote(@_);
- trick_taint($retval) if defined $retval;
- return $retval;
+ my $self = shift;
+ my $retval = $self->dbh->quote(@_);
+ trick_taint($retval) if defined $retval;
+ return $retval;
}
#####################################################################
@@ -133,203 +127,210 @@ sub quote {
#####################################################################
sub connect_shadow {
- state $shadow_dbh;
- if ($shadow_dbh && $shadow_dbh->bz_in_transaction) {
- FATAL("Somehow in a transaction at connection time");
- $shadow_dbh->bz_rollback_transaction();
- }
- return $shadow_dbh if $shadow_dbh;
- my $params = Bugzilla->params;
- die "Tried to connect to non-existent shadowdb"
- unless Bugzilla->get_param_with_override('shadowdb');
-
- # Instead of just passing in a new hashref, we locally modify the
- # values of "localconfig", because some drivers access it while
- # connecting.
- my $connect_params = dclone(Bugzilla->localconfig);
- $connect_params->{db_host} = Bugzilla->get_param_with_override('shadowdbhost');
- $connect_params->{db_name} = Bugzilla->get_param_with_override('shadowdb');
- $connect_params->{db_port} = Bugzilla->get_param_with_override('shadowdbport');
- $connect_params->{db_sock} = Bugzilla->get_param_with_override('shadowdbsock');
-
- if ( Bugzilla->localconfig->{'shadowdb_user'} && Bugzilla->localconfig->{'shadowdb_pass'} ) {
- $connect_params->{db_user} = Bugzilla->localconfig->{'shadowdb_user'};
- $connect_params->{db_pass} = Bugzilla->localconfig->{'shadowdb_pass'};
- }
- return $shadow_dbh = _connect($connect_params);
+ state $shadow_dbh;
+ if ($shadow_dbh && $shadow_dbh->bz_in_transaction) {
+ FATAL("Somehow in a transaction at connection time");
+ $shadow_dbh->bz_rollback_transaction();
+ }
+ return $shadow_dbh if $shadow_dbh;
+ my $params = Bugzilla->params;
+ die "Tried to connect to non-existent shadowdb"
+ unless Bugzilla->get_param_with_override('shadowdb');
+
+ # Instead of just passing in a new hashref, we locally modify the
+ # values of "localconfig", because some drivers access it while
+ # connecting.
+ my $connect_params = dclone(Bugzilla->localconfig);
+ $connect_params->{db_host} = Bugzilla->get_param_with_override('shadowdbhost');
+ $connect_params->{db_name} = Bugzilla->get_param_with_override('shadowdb');
+ $connect_params->{db_port} = Bugzilla->get_param_with_override('shadowdbport');
+ $connect_params->{db_sock} = Bugzilla->get_param_with_override('shadowdbsock');
+
+ if ( Bugzilla->localconfig->{'shadowdb_user'}
+ && Bugzilla->localconfig->{'shadowdb_pass'})
+ {
+ $connect_params->{db_user} = Bugzilla->localconfig->{'shadowdb_user'};
+ $connect_params->{db_pass} = Bugzilla->localconfig->{'shadowdb_pass'};
+ }
+ return $shadow_dbh = _connect($connect_params);
}
sub connect_main {
- state $main_dbh = _connect(Bugzilla->localconfig);
- if ($main_dbh->bz_in_transaction) {
- FATAL("Somehow in a transaction at connection time");
- $main_dbh->bz_rollback_transaction();
- }
- return $main_dbh;
+ state $main_dbh = _connect(Bugzilla->localconfig);
+ if ($main_dbh->bz_in_transaction) {
+ FATAL("Somehow in a transaction at connection time");
+ $main_dbh->bz_rollback_transaction();
+ }
+ return $main_dbh;
}
sub _connect {
- my ($params) = @_;
+ my ($params) = @_;
- my $driver = $params->{db_driver};
- my $pkg_module = DB_MODULE->{lc($driver)}->{db};
+ my $driver = $params->{db_driver};
+ my $pkg_module = DB_MODULE->{lc($driver)}->{db};
- # do the actual import
- eval { require_module($pkg_module) }
- || die ("'$driver' is not a valid choice for \$db_driver in "
- . " localconfig: " . $@);
+ # do the actual import
+ eval { require_module($pkg_module) }
+ || die(
+ "'$driver' is not a valid choice for \$db_driver in " . " localconfig: " . $@);
- # instantiate the correct DB specific module
+ # instantiate the correct DB specific module
- return $pkg_module->new($params);
+ return $pkg_module->new($params);
}
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;
- # BMO: stracktrace disabled:
- # $_[0] = Carp::longmess($_[0]);
-
- # BMO: catch long running query timeouts and translate into a sane message
- #if ($_[0] =~ /Lost connection to MySQL server during query/) {
- # warn(Carp::longmess($_[0]));
- # $_[0] = "The database query took too long to complete and has been canceled.\n"
- # . "(Lost connection to MySQL server during query)";
- #}
-
- #if (Bugzilla->usage_mode == USAGE_MODE_BROWSER) {
- # ThrowCodeError("db_error", { err_message => $_[0] });
- #}
-
- # keep tests happy
- if (0) {
- ThrowCodeError("db_error", { err_message => $_[0] });
- }
+ require Carp;
+
+ # Cut down the error string to a reasonable size
+ $_[0] = substr($_[0], 0, 2000) . ' ... ' . substr($_[0], -2000)
+ if length($_[0]) > 4000;
+
+ # BMO: stracktrace disabled:
+ # $_[0] = Carp::longmess($_[0]);
+
+# BMO: catch long running query timeouts and translate into a sane message
+#if ($_[0] =~ /Lost connection to MySQL server during query/) {
+# warn(Carp::longmess($_[0]));
+# $_[0] = "The database query took too long to complete and has been canceled.\n"
+# . "(Lost connection to MySQL server during query)";
+#}
+
+ #if (Bugzilla->usage_mode == USAGE_MODE_BROWSER) {
+ # ThrowCodeError("db_error", { err_message => $_[0] });
+ #}
+
+ # keep tests happy
+ if (0) {
+ ThrowCodeError("db_error", {err_message => $_[0]});
+ }
- return 0; # Now let DBI handle raising the error
+ return 0; # Now let DBI handle raising the error
}
sub bz_check_requirements {
- my ($output) = @_;
+ my ($output) = @_;
- my $lc = Bugzilla->localconfig;
- my $db = DB_MODULE->{lc($lc->{db_driver})};
+ my $lc = Bugzilla->localconfig;
+ my $db = DB_MODULE->{lc($lc->{db_driver})};
- # Only certain values are allowed for $db_driver.
- if (!defined $db) {
- die "$lc->{db_driver} is not a valid choice for \$db_driver in"
- . bz_locations()->{'localconfig'};
- }
+ # Only certain values are allowed for $db_driver.
+ if (!defined $db) {
+ die "$lc->{db_driver} is not a valid choice for \$db_driver in"
+ . bz_locations()->{'localconfig'};
+ }
- # We don't try to connect to the actual database if $db_check is
- # disabled.
- unless ($lc->{db_check}) {
- print "\n" if $output;
- return;
- }
+ # We don't try to connect to the actual database if $db_check is
+ # disabled.
+ unless ($lc->{db_check}) {
+ print "\n" if $output;
+ return;
+ }
- # And now check the version of the database server itself.
- my $dbh = _get_no_db_connection();
- $dbh->bz_check_server_version($db, $output);
+ # And now check the version of the database server itself.
+ my $dbh = _get_no_db_connection();
+ $dbh->bz_check_server_version($db, $output);
- print "\n" if $output;
+ print "\n" if $output;
}
sub bz_check_server_version {
- my ($self, $db, $output) = @_;
+ my ($self, $db, $output) = @_;
- my $sql_vers = $self->bz_server_version;
+ my $sql_vers = $self->bz_server_version;
- my $sql_want = $db->{db_version};
- my $version_ok = vers_cmp($sql_vers, $sql_want) > -1 ? 1 : 0;
+ my $sql_want = $db->{db_version};
+ my $version_ok = vers_cmp($sql_vers, $sql_want) > -1 ? 1 : 0;
- my $sql_server = $db->{name};
- if ($output) {
- Bugzilla::Install::Requirements::_checking_for({
- package => $sql_server, wanted => $sql_want,
- found => $sql_vers, ok => $version_ok });
- }
+ my $sql_server = $db->{name};
+ if ($output) {
+ Bugzilla::Install::Requirements::_checking_for({
+ package => $sql_server,
+ wanted => $sql_want,
+ found => $sql_vers,
+ ok => $version_ok
+ });
+ }
- # Check what version of the database server is installed and let
- # the user know if the version is too old to be used with Bugzilla.
- if (!$version_ok) {
- die <<EOT;
+ # Check what version of the database server is installed and let
+ # the user know if the version is too old to be used with Bugzilla.
+ if (!$version_ok) {
+ die <<EOT;
Your $sql_server v$sql_vers is too old. Bugzilla requires version
$sql_want or later of $sql_server. Please download and install a
newer version.
EOT
- }
+ }
- # This is used by subclasses.
- return $sql_vers;
+ # This is used by subclasses.
+ return $sql_vers;
}
# Note that this function requires that localconfig exist and
# be valid.
sub bz_create_database {
- my $dbh;
- # See if we can connect to the actual Bugzilla database.
- my $conn_success = eval { $dbh = connect_main() };
- my $db_name = Bugzilla->localconfig->{db_name};
-
- if (!$conn_success) {
- $dbh = _get_no_db_connection();
- print "Creating database $db_name...\n";
-
- # Try to create the DB, and if we fail print a friendly error.
- my $success = eval {
- my @sql = $dbh->_bz_schema->get_create_database_sql($db_name);
- # This ends with 1 because this particular do doesn't always
- # return something.
- $dbh->do($_) foreach @sql; 1;
- };
- if (!$success) {
- my $error = $dbh->errstr || $@;
- chomp($error);
- die "The '$db_name' database could not be created.",
- " The error returned was:\n\n $error\n\n",
- _bz_connect_error_reasons();
- }
+ my $dbh;
+
+ # See if we can connect to the actual Bugzilla database.
+ my $conn_success = eval { $dbh = connect_main() };
+ my $db_name = Bugzilla->localconfig->{db_name};
+
+ if (!$conn_success) {
+ $dbh = _get_no_db_connection();
+ print "Creating database $db_name...\n";
+
+ # Try to create the DB, and if we fail print a friendly error.
+ my $success = eval {
+ my @sql = $dbh->_bz_schema->get_create_database_sql($db_name);
+
+ # This ends with 1 because this particular do doesn't always
+ # return something.
+ $dbh->do($_) foreach @sql;
+ 1;
+ };
+ if (!$success) {
+ my $error = $dbh->errstr || $@;
+ chomp($error);
+ die "The '$db_name' database could not be created.",
+ " The error returned was:\n\n $error\n\n", _bz_connect_error_reasons();
}
+ }
}
# A helper for bz_create_database and bz_check_requirements.
sub _get_no_db_connection {
- my ($sql_server) = @_;
- my $dbh;
- my %connect_params = %{ Bugzilla->localconfig };
- $connect_params{db_name} = '';
- my $conn_success = eval {
- $dbh = _connect(\%connect_params);
- };
- if (!$conn_success) {
- my $driver = $connect_params{db_driver};
- my $sql_server = DB_MODULE->{lc($driver)}->{name};
- # Can't use $dbh->errstr because $dbh is undef.
- my $error = $DBI::errstr || $@;
- chomp($error);
- die "There was an error connecting to $sql_server:\n\n",
- " $error\n\n", _bz_connect_error_reasons(), "\n";
- }
- return $dbh;
+ my ($sql_server) = @_;
+ my $dbh;
+ my %connect_params = %{Bugzilla->localconfig};
+ $connect_params{db_name} = '';
+ my $conn_success = eval { $dbh = _connect(\%connect_params); };
+ if (!$conn_success) {
+ my $driver = $connect_params{db_driver};
+ my $sql_server = DB_MODULE->{lc($driver)}->{name};
+
+ # Can't use $dbh->errstr because $dbh is undef.
+ my $error = $DBI::errstr || $@;
+ chomp($error);
+ die "There was an error connecting to $sql_server:\n\n", " $error\n\n",
+ _bz_connect_error_reasons(), "\n";
+ }
+ return $dbh;
}
# Just a helper because we have to re-use this text.
# We don't use this in db_new because it gives away the database
# username, and db_new errors can show up on CGIs.
sub _bz_connect_error_reasons {
- my $lc_file = bz_locations()->{'localconfig'};
- my $lc = Bugzilla->localconfig;
- my $db = DB_MODULE->{lc($lc->{db_driver})};
- my $server = $db->{name};
+ my $lc_file = bz_locations()->{'localconfig'};
+ my $lc = Bugzilla->localconfig;
+ my $db = DB_MODULE->{lc($lc->{db_driver})};
+ my $server = $db->{name};
-return <<EOT;
+ return <<EOT;
This might have several reasons:
* $server is not running.
@@ -348,137 +349,140 @@ EOT
# 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_date_math bz_explain
- sql_group_concat);
+ sql_date_format sql_date_math bz_explain
+ sql_group_concat);
# This overridden 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 die("Class $pkg does not define method $meth");
- }
+ 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 die("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;
+ # 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_prefix_match {
- my ($self, $column, $str) = @_;
- my $must_escape = $str =~ s/([_%!])/!$1/g;
- my $escape = $must_escape ? q/ESCAPE '!'/ : '';
- my $quoted_str = $self->quote("$str%");
- return "$column LIKE $quoted_str $escape";
+ my ($self, $column, $str) = @_;
+ my $must_escape = $str =~ s/([_%!])/!$1/g;
+ my $escape = $must_escape ? q/ESCAPE '!'/ : '';
+ my $quoted_str = $self->quote("$str%");
+ return "$column LIKE $quoted_str $escape";
}
sub sql_istrcmp {
- my ($self, $left, $right, $op) = @_;
- $op ||= "=";
+ my ($self, $left, $right, $op) = @_;
+ $op ||= "=";
- return $self->sql_istring($left) . " $op " . $self->sql_istring($right);
+ return $self->sql_istring($left) . " $op " . $self->sql_istring($right);
}
sub sql_istring {
- my ($self, $string) = @_;
+ my ($self, $string) = @_;
- return "LOWER($string)";
+ return "LOWER($string)";
}
sub sql_iposition {
- my ($self, $fragment, $text) = @_;
- $fragment = $self->sql_istring($fragment);
- $text = $self->sql_istring($text);
- return $self->sql_position($fragment, $text);
+ my ($self, $fragment, $text) = @_;
+ $fragment = $self->sql_istring($fragment);
+ $text = $self->sql_istring($text);
+ return $self->sql_position($fragment, $text);
}
sub sql_position {
- my ($self, $fragment, $text) = @_;
+ my ($self, $fragment, $text) = @_;
- return "POSITION($fragment IN $text)";
+ return "POSITION($fragment IN $text)";
}
sub sql_group_by {
- my ($self, $needed_columns, $optional_columns) = @_;
+ my ($self, $needed_columns, $optional_columns) = @_;
- my $expression = "GROUP BY $needed_columns";
- $expression .= ", " . $optional_columns if $optional_columns;
+ my $expression = "GROUP BY $needed_columns";
+ $expression .= ", " . $optional_columns if $optional_columns;
- return $expression;
+ return $expression;
}
sub sql_string_concat {
- my ($self, @params) = @_;
+ my ($self, @params) = @_;
- return '(' . join(' || ', @params) . ')';
+ return '(' . join(' || ', @params) . ')';
}
sub sql_string_until {
- my ($self, $string, $substring) = @_;
+ my ($self, $string, $substring) = @_;
- my $position = $self->sql_position($substring, $string);
- return "CASE WHEN $position != 0"
- . " THEN SUBSTR($string, 1, $position - 1)"
- . " ELSE $string END";
+ my $position = $self->sql_position($substring, $string);
+ return
+ "CASE WHEN $position != 0"
+ . " THEN SUBSTR($string, 1, $position - 1)"
+ . " ELSE $string END";
}
sub sql_in {
- my ($self, $column_name, $in_list_ref, $negate) = @_;
- return " $column_name "
- . ($negate ? "NOT " : "")
- . "IN (" . join(',', @$in_list_ref) . ") ";
+ my ($self, $column_name, $in_list_ref, $negate) = @_;
+ return
+ " $column_name "
+ . ($negate ? "NOT " : "") . "IN ("
+ . join(',', @$in_list_ref) . ") ";
}
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're searching for into separate words. As a hack
- # to allow quicksearch to work, if the field starts and ends with
- # a double-quote, then we don't split it into words. We can't use
- # Text::ParseWords here because it gets very confused by unbalanced
- # quotes, which breaks searches like "don't try this" (because of the
- # unbalanced single-quote in "don't").
- my @words;
- if ($lower_text =~ /^"/ and $lower_text =~ /"$/) {
- $lower_text =~ s/^"//;
- $lower_text =~ s/"$//;
- @words = ($lower_text);
- }
- else {
- @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
- trick_taint($_) foreach @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 join (" AND ", @words), "CASE WHEN (" . join(" AND ", @words) . ") THEN 1 ELSE 0 END";
+ 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're searching for into separate words. As a hack
+ # to allow quicksearch to work, if the field starts and ends with
+ # a double-quote, then we don't split it into words. We can't use
+ # Text::ParseWords here because it gets very confused by unbalanced
+ # quotes, which breaks searches like "don't try this" (because of the
+ # unbalanced single-quote in "don't").
+ my @words;
+ if ($lower_text =~ /^"/ and $lower_text =~ /"$/) {
+ $lower_text =~ s/^"//;
+ $lower_text =~ s/"$//;
+ @words = ($lower_text);
+ }
+ else {
+ @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
+ trick_taint($_) foreach @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 join(" AND ", @words),
+ "CASE WHEN (" . join(" AND ", @words) . ") THEN 1 ELSE 0 END";
}
#####################################################################
@@ -487,24 +491,27 @@ sub sql_fulltext_search {
# XXX - Needs to be documented.
sub bz_server_version {
- my ($self) = @_;
- return $self->get_info(18); # SQL_DBMS_VER
+ my ($self) = @_;
+ return $self->get_info(18); # SQL_DBMS_VER
}
sub bz_last_key {
- my ($self, $table, $column) = @_;
+ my ($self, $table, $column) = @_;
- return $self->last_insert_id(Bugzilla->localconfig->{db_name}, undef,
- $table, $column);
+ return $self->last_insert_id(Bugzilla->localconfig->{db_name},
+ undef, $table, $column);
}
sub bz_check_regexp {
- my ($self, $pattern) = @_;
+ my ($self, $pattern) = @_;
- eval { $self->do("SELECT " . $self->sql_regexp($self->quote("a"), $pattern, 1)) };
+ eval {
+ $self->do("SELECT " . $self->sql_regexp($self->quote("a"), $pattern, 1));
+ };
- $@ && ThrowUserError('illegal_regexp',
- { value => $pattern, dberror => $self->errstr });
+ $@
+ && ThrowUserError('illegal_regexp',
+ {value => $pattern, dberror => $self->errstr});
}
#####################################################################
@@ -512,99 +519,100 @@ sub bz_check_regexp {
#####################################################################
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();
-
- # We don't use bz_table_list here, because that uses _bz_real_schema.
- # We actually want the table list from the ABSTRACT_SCHEMA in
- # Bugzilla::DB::Schema.
- my @desired_tables = $self->_bz_schema->get_table_list();
- my $bugs_exists = $self->bz_table_info('bugs');
- if (!$bugs_exists) {
- print install_string('db_table_setup'), "\n";
- }
+ my ($self) = @_;
- foreach my $table_name (@desired_tables) {
- $self->bz_add_table($table_name, { silently => !$bugs_exists });
- }
+ # If we haven't ever stored a serialized schema,
+ # set up the bz_schema table and store it.
+ $self->_bz_init_schema_storage();
+
+ # We don't use bz_table_list here, because that uses _bz_real_schema.
+ # We actually want the table list from the ABSTRACT_SCHEMA in
+ # Bugzilla::DB::Schema.
+ my @desired_tables = $self->_bz_schema->get_table_list();
+ my $bugs_exists = $self->bz_table_info('bugs');
+ if (!$bugs_exists) {
+ print install_string('db_table_setup'), "\n";
+ }
+
+ foreach my $table_name (@desired_tables) {
+ $self->bz_add_table($table_name, {silently => !$bugs_exists});
+ }
}
# This really just exists to get overridden in Bugzilla::DB::Mysql.
sub bz_enum_initial_values {
- return ENUM_DEFAULTS;
+ return ENUM_DEFAULTS;
}
sub bz_populate_enum_tables {
- my ($self) = @_;
+ my ($self) = @_;
- my $any_severities = $self->selectrow_array(
- 'SELECT 1 FROM bug_severity ' . $self->sql_limit(1));
- print install_string('db_enum_setup'), "\n " if !$any_severities;
+ my $any_severities
+ = $self->selectrow_array('SELECT 1 FROM bug_severity ' . $self->sql_limit(1));
+ print install_string('db_enum_setup'), "\n " if !$any_severities;
- my $enum_values = $self->bz_enum_initial_values();
- while (my ($table, $values) = each %$enum_values) {
- $self->_bz_populate_enum_table($table, $values);
- }
+ my $enum_values = $self->bz_enum_initial_values();
+ while (my ($table, $values) = each %$enum_values) {
+ $self->_bz_populate_enum_table($table, $values);
+ }
- print "\n" if !$any_severities;
+ print "\n" if !$any_severities;
}
sub bz_setup_foreign_keys {
- my ($self) = @_;
-
- # profiles_activity was the first table to get foreign keys,
- # so if it doesn't have them, then we're setting up FKs
- # for the first time, and should be quieter about it.
- my $activity_fk = $self->bz_fk_info('profiles_activity', 'userid');
- my $any_fks = $activity_fk && $activity_fk->{created};
- if (!$any_fks) {
- print get_text('install_fk_setup'), "\n";
- }
+ my ($self) = @_;
+
+ # profiles_activity was the first table to get foreign keys,
+ # so if it doesn't have them, then we're setting up FKs
+ # for the first time, and should be quieter about it.
+ my $activity_fk = $self->bz_fk_info('profiles_activity', 'userid');
+ my $any_fks = $activity_fk && $activity_fk->{created};
+ if (!$any_fks) {
+ print get_text('install_fk_setup'), "\n";
+ }
+
+ my @tables = $self->bz_table_list();
+ foreach my $table (@tables) {
+ my @columns = $self->bz_table_columns($table);
+ my %add_fks;
+ foreach my $column (@columns) {
- my @tables = $self->bz_table_list();
- foreach my $table (@tables) {
- my @columns = $self->bz_table_columns($table);
- my %add_fks;
- foreach my $column (@columns) {
- # First we check for any FKs that have created => 0,
- # in the _bz_real_schema. This also picks up FKs with
- # created => 1, but bz_add_fks will ignore those.
- my $fk = $self->bz_fk_info($table, $column);
- # Then we check the abstract schema to see if there
- # should be an FK on this column, but one wasn't set in the
- # _bz_real_schema for some reason. We do this to handle
- # various problems caused by upgrading from versions
- # prior to 4.2, and also to handle problems caused
- # by enabling an extension pre-4.2, disabling it for
- # the 4.2 upgrade, and then re-enabling it later.
- unless ($fk && $fk->{created}) {
- my $standard_def =
- $self->_bz_schema->get_column_abstract($table, $column);
- if (exists $standard_def->{REFERENCES}) {
- $fk = dclone($standard_def->{REFERENCES});
- }
- }
-
- $add_fks{$column} = $fk if $fk;
+ # First we check for any FKs that have created => 0,
+ # in the _bz_real_schema. This also picks up FKs with
+ # created => 1, but bz_add_fks will ignore those.
+ my $fk = $self->bz_fk_info($table, $column);
+
+ # Then we check the abstract schema to see if there
+ # should be an FK on this column, but one wasn't set in the
+ # _bz_real_schema for some reason. We do this to handle
+ # various problems caused by upgrading from versions
+ # prior to 4.2, and also to handle problems caused
+ # by enabling an extension pre-4.2, disabling it for
+ # the 4.2 upgrade, and then re-enabling it later.
+ unless ($fk && $fk->{created}) {
+ my $standard_def = $self->_bz_schema->get_column_abstract($table, $column);
+ if (exists $standard_def->{REFERENCES}) {
+ $fk = dclone($standard_def->{REFERENCES});
}
- $self->bz_add_fks($table, \%add_fks, { silently => !$any_fks });
+ }
+
+ $add_fks{$column} = $fk if $fk;
}
+ $self->bz_add_fks($table, \%add_fks, {silently => !$any_fks});
+ }
}
# This is used by contrib/bzdbcopy.pl, mostly.
sub bz_drop_foreign_keys {
- my ($self) = @_;
+ my ($self) = @_;
- my @tables = $self->bz_table_list();
- foreach my $table (@tables) {
- my @columns = $self->bz_table_columns($table);
- foreach my $column (@columns) {
- $self->bz_drop_fk($table, $column);
- }
+ my @tables = $self->bz_table_list();
+ foreach my $table (@tables) {
+ my @columns = $self->bz_table_columns($table);
+ foreach my $column (@columns) {
+ $self->bz_drop_fk($table, $column);
}
+ }
}
#####################################################################
@@ -612,119 +620,121 @@ sub bz_drop_foreign_keys {
#####################################################################
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/)
- {
- ThrowCodeError('column_not_null_without_default',
- { name => "$table.$name" });
+ 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/)
+ {
+ ThrowCodeError('column_not_null_without_default', {name => "$table.$name"});
+ }
+
+ my $current_def = $self->bz_column_info($table, $name);
+
+ if (!$current_def) {
+
+ # REFERENCES need to happen later and not be created right away
+ my $trimmed_def = dclone($new_def);
+ delete $trimmed_def->{REFERENCES};
+ my @statements
+ = $self->_bz_real_schema->get_add_column_ddl($table, $name, $trimmed_def,
+ defined $init_value ? $self->quote($init_value) : undef);
+ print get_text('install_column_add', {column => $name, table => $table}) . "\n"
+ if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
+ foreach my $sql (@statements) {
+ $self->do($sql);
}
- my $current_def = $self->bz_column_info($table, $name);
-
- if (!$current_def) {
- # REFERENCES need to happen later and not be created right away
- my $trimmed_def = dclone($new_def);
- delete $trimmed_def->{REFERENCES};
- my @statements = $self->_bz_real_schema->get_add_column_ddl(
- $table, $name, $trimmed_def,
- defined $init_value ? $self->quote($init_value) : undef);
- print get_text('install_column_add',
- { column => $name, table => $table }) . "\n"
- if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
- foreach my $sql (@statements) {
- $self->do($sql);
- }
-
- # To make things easier for callers, if they don't specify
- # a REFERENCES item, we pull it from the _bz_schema if the
- # column exists there and has a REFERENCES item.
- # bz_setup_foreign_keys will then add this FK at the end of
- # Install::DB.
- my $col_abstract =
- $self->_bz_schema->get_column_abstract($table, $name);
- if (exists $col_abstract->{REFERENCES}) {
- my $new_fk = dclone($col_abstract->{REFERENCES});
- $new_fk->{created} = 0;
- $new_def->{REFERENCES} = $new_fk;
- }
-
- $self->_bz_real_schema->set_column($table, $name, $new_def);
- $self->_bz_store_real_schema;
+ # To make things easier for callers, if they don't specify
+ # a REFERENCES item, we pull it from the _bz_schema if the
+ # column exists there and has a REFERENCES item.
+ # bz_setup_foreign_keys will then add this FK at the end of
+ # Install::DB.
+ my $col_abstract = $self->_bz_schema->get_column_abstract($table, $name);
+ if (exists $col_abstract->{REFERENCES}) {
+ my $new_fk = dclone($col_abstract->{REFERENCES});
+ $new_fk->{created} = 0;
+ $new_def->{REFERENCES} = $new_fk;
}
+
+ $self->_bz_real_schema->set_column($table, $name, $new_def);
+ $self->_bz_store_real_schema;
+ }
}
sub bz_add_fk {
- my ($self, $table, $column, $def) = @_;
- $self->bz_add_fks($table, { $column => $def });
+ my ($self, $table, $column, $def) = @_;
+ $self->bz_add_fks($table, {$column => $def});
}
sub bz_add_fks {
- my ($self, $table, $column_fks, $options) = @_;
-
- my %add_these;
- foreach my $column (keys %$column_fks) {
- my $current_fk = $self->bz_fk_info($table, $column);
- next if ($current_fk and $current_fk->{created});
- my $new_fk = $column_fks->{$column};
- $self->_check_references($table, $column, $new_fk);
- $add_these{$column} = $new_fk;
- if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE
- and !$options->{silently})
- {
- print get_text('install_fk_add',
- { table => $table, column => $column,
- fk => $new_fk }), "\n";
- }
+ my ($self, $table, $column_fks, $options) = @_;
+
+ my %add_these;
+ foreach my $column (keys %$column_fks) {
+ my $current_fk = $self->bz_fk_info($table, $column);
+ next if ($current_fk and $current_fk->{created});
+ my $new_fk = $column_fks->{$column};
+ $self->_check_references($table, $column, $new_fk);
+ $add_these{$column} = $new_fk;
+ if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE and !$options->{silently}) {
+ print get_text(
+ 'install_fk_add', {table => $table, column => $column, fk => $new_fk}
+ ),
+ "\n";
}
+ }
- return if !scalar(keys %add_these);
+ return if !scalar(keys %add_these);
- my @sql = $self->_bz_real_schema->get_add_fks_sql($table, \%add_these);
- $self->do($_) foreach @sql;
+ my @sql = $self->_bz_real_schema->get_add_fks_sql($table, \%add_these);
+ $self->do($_) foreach @sql;
- foreach my $column (keys %add_these) {
- my $fk_def = $add_these{$column};
- $fk_def->{created} = 1;
- $self->_bz_real_schema->set_fk($table, $column, $fk_def);
- }
+ foreach my $column (keys %add_these) {
+ my $fk_def = $add_these{$column};
+ $fk_def->{created} = 1;
+ $self->_bz_real_schema->set_fk($table, $column, $fk_def);
+ }
- $self->_bz_store_real_schema();
+ $self->_bz_store_real_schema();
}
sub bz_alter_column {
- my ($self, $table, $name, $new_def, $set_nulls_to) = @_;
+ my ($self, $table, $name, $new_def, $set_nulls_to) = @_;
- my $current_def = $self->bz_column_info($table, $name);
+ 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");
- ThrowCodeError('column_not_null_no_default_alter',
- { name => "$table.$name" }) if ($any_nulls);
- }
- # Preserve foreign key definitions in the Schema object when altering
- # types.
- if (my $fk = $self->bz_fk_info($table, $name)) {
- $new_def->{REFERENCES} = $fk;
- }
- $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;
+ 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");
+ ThrowCodeError('column_not_null_no_default_alter', {name => "$table.$name"})
+ if ($any_nulls);
}
+
+ # Preserve foreign key definitions in the Schema object when altering
+ # types.
+ if (my $fk = $self->bz_fk_info($table, $name)) {
+ $new_def->{REFERENCES} = $fk;
+ }
+ $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;
+ }
}
@@ -750,39 +760,40 @@ sub 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);
+ 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_alter_fk {
- my ($self, $table, $column, $fk_def) = @_;
- my $current_fk = $self->bz_fk_info($table, $column);
- ThrowCodeError('column_alter_nonexistent_fk',
- { table => $table, column => $column }) if !$current_fk;
- $self->bz_drop_fk($table, $column);
- $self->bz_add_fk($table, $column, $fk_def);
+ my ($self, $table, $column, $fk_def) = @_;
+ my $current_fk = $self->bz_fk_info($table, $column);
+ ThrowCodeError('column_alter_nonexistent_fk',
+ {table => $table, column => $column})
+ if !$current_fk;
+ $self->bz_drop_fk($table, $column);
+ $self->bz_add_fk($table, $column, $fk_def);
}
sub bz_add_index {
- my ($self, $table, $name, $definition) = @_;
+ my ($self, $table, $name, $definition) = @_;
- my $index_exists = $self->bz_index_info($table, $name);
+ 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;
- }
+ 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)
@@ -802,36 +813,36 @@ sub bz_add_index {
# 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);
+ 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, $options) = @_;
-
- my $table_exists = $self->bz_table_info($name);
-
- if (!$table_exists) {
- $self->_bz_add_table_raw($name, $options);
- my $table_def = dclone($self->_bz_schema->get_table_abstract($name));
-
- my %fields = @{$table_def->{FIELDS}};
- foreach my $col (keys %fields) {
- # Foreign Key references have to be added by Install::DB after
- # initial table creation, because column names have changed
- # over history and it's impossible to keep track of that info
- # in ABSTRACT_SCHEMA.
- next unless exists $fields{$col}->{REFERENCES};
- $fields{$col}->{REFERENCES}->{created} =
- $self->_bz_real_schema->FK_ON_CREATE;
- }
+ my ($self, $name, $options) = @_;
+
+ my $table_exists = $self->bz_table_info($name);
+
+ if (!$table_exists) {
+ $self->_bz_add_table_raw($name, $options);
+ my $table_def = dclone($self->_bz_schema->get_table_abstract($name));
- $self->_bz_real_schema->add_table($name, $table_def);
- $self->_bz_store_real_schema;
+ my %fields = @{$table_def->{FIELDS}};
+ foreach my $col (keys %fields) {
+
+ # Foreign Key references have to be added by Install::DB after
+ # initial table creation, because column names have changed
+ # over history and it's impossible to keep track of that info
+ # in ABSTRACT_SCHEMA.
+ next unless exists $fields{$col}->{REFERENCES};
+ $fields{$col}->{REFERENCES}->{created} = $self->_bz_real_schema->FK_ON_CREATE;
}
+
+ $self->_bz_real_schema->add_table($name, $table_def);
+ $self->_bz_store_real_schema;
+ }
}
# _bz_add_table_raw($name) - Private
@@ -849,158 +860,168 @@ sub bz_add_table {
# Returns: nothing
#
sub _bz_add_table_raw {
- my ($self, $name, $options) = @_;
- my @statements = $self->_bz_schema->get_table_ddl($name);
- if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE
- and !$options->{silently})
- {
- print install_string('db_table_new', { table => $name }), "\n";
- }
- $self->do($_) foreach (@statements);
+ my ($self, $name, $options) = @_;
+ my @statements = $self->_bz_schema->get_table_ddl($name);
+ if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE and !$options->{silently}) {
+ print install_string('db_table_new', {table => $name}), "\n";
+ }
+ $self->do($_) foreach (@statements);
}
sub _bz_add_field_table {
- my ($self, $name, $schema_ref) = @_;
- # We do nothing if the table already exists.
- return if $self->bz_table_info($name);
-
- # Copy this so that we're not modifying the passed reference.
- # (This avoids modifying a constant in Bugzilla::DB::Schema.)
- my %table_schema = %$schema_ref;
- my %indexes = @{ $table_schema{INDEXES} };
- my %fixed_indexes;
- foreach my $key (keys %indexes) {
- $fixed_indexes{$name . "_" . $key} = $indexes{$key};
- }
- # INDEXES is supposed to be an arrayref, so we have to convert back.
- my @indexes_array = %fixed_indexes;
- $table_schema{INDEXES} = \@indexes_array;
- # We add this to the abstract schema so that bz_add_table can find it.
- $self->_bz_schema->add_table($name, \%table_schema);
- $self->bz_add_table($name);
-}
+ my ($self, $name, $schema_ref) = @_;
-sub bz_add_field_tables {
- my ($self, $field) = @_;
+ # We do nothing if the table already exists.
+ return if $self->bz_table_info($name);
- $self->_bz_add_field_table($field->name,
- $self->_bz_schema->FIELD_TABLE_SCHEMA, $field->type);
- if ($field->type == FIELD_TYPE_MULTI_SELECT) {
- my $ms_table = "bug_" . $field->name;
- $self->_bz_add_field_table($ms_table,
- $self->_bz_schema->MULTI_SELECT_VALUE_TABLE);
+ # Copy this so that we're not modifying the passed reference.
+ # (This avoids modifying a constant in Bugzilla::DB::Schema.)
+ my %table_schema = %$schema_ref;
+ my %indexes = @{$table_schema{INDEXES}};
+ my %fixed_indexes;
+ foreach my $key (keys %indexes) {
+ $fixed_indexes{$name . "_" . $key} = $indexes{$key};
+ }
- $self->bz_add_fks($ms_table,
- { bug_id => {TABLE => 'bugs', COLUMN => 'bug_id',
- DELETE => 'CASCADE'},
+ # INDEXES is supposed to be an arrayref, so we have to convert back.
+ my @indexes_array = %fixed_indexes;
+ $table_schema{INDEXES} = \@indexes_array;
- value => {TABLE => $field->name, COLUMN => 'value'} });
- }
+ # We add this to the abstract schema so that bz_add_table can find it.
+ $self->_bz_schema->add_table($name, \%table_schema);
+ $self->bz_add_table($name);
+}
+
+sub bz_add_field_tables {
+ my ($self, $field) = @_;
+
+ $self->_bz_add_field_table($field->name, $self->_bz_schema->FIELD_TABLE_SCHEMA,
+ $field->type);
+ if ($field->type == FIELD_TYPE_MULTI_SELECT) {
+ my $ms_table = "bug_" . $field->name;
+ $self->_bz_add_field_table($ms_table,
+ $self->_bz_schema->MULTI_SELECT_VALUE_TABLE);
+
+ $self->bz_add_fks(
+ $ms_table,
+ {
+ bug_id => {TABLE => 'bugs', COLUMN => 'bug_id', DELETE => 'CASCADE'},
+
+ value => {TABLE => $field->name, COLUMN => 'value'}
+ }
+ );
+ }
}
sub bz_drop_field_tables {
- my ($self, $field) = @_;
- if ($field->type == FIELD_TYPE_MULTI_SELECT) {
- $self->bz_drop_table('bug_' . $field->name);
- }
- $self->bz_drop_table($field->name);
+ my ($self, $field) = @_;
+ if ($field->type == FIELD_TYPE_MULTI_SELECT) {
+ $self->bz_drop_table('bug_' . $field->name);
+ }
+ $self->bz_drop_table($field->name);
}
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 get_text('install_column_drop',
- { table => $table, column => $column }) . "\n"
- if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
- 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;
+ 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 get_text('install_column_drop', {table => $table, column => $column})
+ . "\n"
+ if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
+ 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_fk {
- my ($self, $table, $column) = @_;
-
- my $fk_def = $self->bz_fk_info($table, $column);
- if ($fk_def and $fk_def->{created}) {
- print get_text('install_fk_drop',
- { table => $table, column => $column, fk => $fk_def })
- . "\n" if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
- my @statements =
- $self->_bz_real_schema->get_drop_fk_sql($table, $column, $fk_def);
- 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: $@";
- }
- # Under normal circumstances, we don't permanently drop the fk--
- # we want checksetup to re-create it again later. The only
- # time that FKs get permanently dropped is if the column gets
- # dropped.
- $fk_def->{created} = 0;
- $self->_bz_real_schema->set_fk($table, $column, $fk_def);
- $self->_bz_store_real_schema;
+ my ($self, $table, $column) = @_;
+
+ my $fk_def = $self->bz_fk_info($table, $column);
+ if ($fk_def and $fk_def->{created}) {
+ print get_text('install_fk_drop',
+ {table => $table, column => $column, fk => $fk_def})
+ . "\n"
+ if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
+ my @statements
+ = $self->_bz_real_schema->get_drop_fk_sql($table, $column, $fk_def);
+ 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: $@";
}
+ # Under normal circumstances, we don't permanently drop the fk--
+ # we want checksetup to re-create it again later. The only
+ # time that FKs get permanently dropped is if the column gets
+ # dropped.
+ $fk_def->{created} = 0;
+ $self->_bz_real_schema->set_fk($table, $column, $fk_def);
+ $self->_bz_store_real_schema;
+ }
+
}
sub bz_get_related_fks {
- my ($self, $table, $column) = @_;
- my @tables = $self->_bz_real_schema->get_table_list();
- my @related;
- foreach my $check_table (@tables) {
- my @columns = $self->bz_table_columns($check_table);
- foreach my $check_column (@columns) {
- my $fk = $self->bz_fk_info($check_table, $check_column);
- if ($fk
- and (($fk->{TABLE} eq $table and $fk->{COLUMN} eq $column)
- or ($check_column eq $column and $check_table eq $table)))
- {
- push(@related, [$check_table, $check_column, $fk]);
- }
- } # foreach $column
- } # foreach $table
-
- return \@related;
+ my ($self, $table, $column) = @_;
+ my @tables = $self->_bz_real_schema->get_table_list();
+ my @related;
+ foreach my $check_table (@tables) {
+ my @columns = $self->bz_table_columns($check_table);
+ foreach my $check_column (@columns) {
+ my $fk = $self->bz_fk_info($check_table, $check_column);
+ if (
+ $fk
+ and (($fk->{TABLE} eq $table and $fk->{COLUMN} eq $column)
+ or ($check_column eq $column and $check_table eq $table))
+ )
+ {
+ push(@related, [$check_table, $check_column, $fk]);
+ }
+ } # foreach $column
+ } # foreach $table
+
+ return \@related;
}
sub bz_drop_related_fks {
- my $self = shift;
- my $related = $self->bz_get_related_fks(@_);
- foreach my $item (@$related) {
- my ($table, $column) = @$item;
- $self->bz_drop_fk($table, $column);
- }
- return $related;
+ my $self = shift;
+ my $related = $self->bz_get_related_fks(@_);
+ foreach my $item (@$related) {
+ my ($table, $column) = @$item;
+ $self->bz_drop_fk($table, $column);
+ }
+ return $related;
}
sub bz_drop_index {
- my ($self, $table, $name) = @_;
+ my ($self, $table, $name) = @_;
- my $index_exists = $self->bz_index_info($table, $name);
+ my $index_exists = $self->bz_index_info($table, $name);
- if ($index_exists) {
- if ($self->INDEX_DROPS_REQUIRE_FK_DROPS) {
- # We cannot delete an index used by a FK.
- foreach my $column (@{$index_exists->{FIELDS}}) {
- $self->bz_drop_related_fks($table, $column);
- }
- }
- $self->bz_drop_index_raw($table, $name);
- $self->_bz_real_schema->delete_index($table, $name);
- $self->_bz_store_real_schema;
+ if ($index_exists) {
+ if ($self->INDEX_DROPS_REQUIRE_FK_DROPS) {
+
+ # We cannot delete an index used by a FK.
+ foreach my $column (@{$index_exists->{FIELDS}}) {
+ $self->bz_drop_related_fks($table, $column);
+ }
}
+ $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)
@@ -1020,108 +1041,111 @@ sub bz_drop_index {
# 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: $@";
- }
+ 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 get_text('install_table_drop', { name => $name }) . "\n"
- if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
- 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;
+ 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 get_text('install_table_drop', {name => $name}) . "\n"
+ if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
+ 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_fk_info {
- my ($self, $table, $column) = @_;
- my $col_info = $self->bz_column_info($table, $column);
- return undef if !$col_info;
- my $fk = $col_info->{REFERENCES};
- return $fk;
+ my ($self, $table, $column) = @_;
+ my $col_info = $self->bz_column_info($table, $column);
+ return undef if !$col_info;
+ my $fk = $col_info->{REFERENCES};
+ return $fk;
}
sub bz_rename_column {
- my ($self, $table, $old_name, $new_name) = @_;
+ my ($self, $table, $old_name, $new_name) = @_;
- my $old_col_exists = $self->bz_column_info($table, $old_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);
- ThrowCodeError('db_rename_conflict',
- { old => "$table.$old_name",
- new => "$table.$new_name" }) if $already_renamed;
- my @statements = $self->_bz_real_schema->get_rename_column_ddl(
- $table, $old_name, $new_name);
+ if ($old_col_exists) {
+ my $already_renamed = $self->bz_column_info($table, $new_name);
+ ThrowCodeError('db_rename_conflict',
+ {old => "$table.$old_name", new => "$table.$new_name"})
+ if $already_renamed;
+ my @statements
+ = $self->_bz_real_schema->get_rename_column_ddl($table, $old_name, $new_name);
- print get_text('install_column_rename',
- { old => "$table.$old_name", new => "$table.$new_name" })
- . "\n" if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
+ print get_text('install_column_rename',
+ {old => "$table.$old_name", new => "$table.$new_name"})
+ . "\n"
+ if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
- foreach my $sql (@statements) {
- $self->do($sql);
- }
- $self->_bz_real_schema->rename_column($table, $old_name, $new_name);
- $self->_bz_store_real_schema;
+ foreach my $sql (@statements) {
+ $self->do($sql);
}
+ $self->_bz_real_schema->rename_column($table, $old_name, $new_name);
+ $self->_bz_store_real_schema;
+ }
}
sub bz_rename_table {
- my ($self, $old_name, $new_name) = @_;
- my $old_table = $self->bz_table_info($old_name);
- return if !$old_table;
-
- my $new = $self->bz_table_info($new_name);
- ThrowCodeError('db_rename_conflict', { old => $old_name,
- new => $new_name }) if $new;
-
- # FKs will all have the wrong names unless we drop and then let them
- # be re-created later. Under normal circumstances, checksetup.pl will
- # automatically re-create these dropped FKs at the end of its DB upgrade
- # run, so we don't need to re-create them in this method.
- my @columns = $self->bz_table_columns($old_name);
- foreach my $column (@columns) {
- # these just return silently if there's no FK to drop
- $self->bz_drop_fk($old_name, $column);
- $self->bz_drop_related_fks($old_name, $column);
- }
-
- my @sql = $self->_bz_real_schema->get_rename_table_sql($old_name, $new_name);
- print get_text('install_table_rename',
- { old => $old_name, new => $new_name }) . "\n"
- if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
- $self->do($_) foreach @sql;
- $self->_bz_real_schema->rename_table($old_name, $new_name);
- $self->_bz_store_real_schema;
+ my ($self, $old_name, $new_name) = @_;
+ my $old_table = $self->bz_table_info($old_name);
+ return if !$old_table;
+
+ my $new = $self->bz_table_info($new_name);
+ ThrowCodeError('db_rename_conflict', {old => $old_name, new => $new_name})
+ if $new;
+
+ # FKs will all have the wrong names unless we drop and then let them
+ # be re-created later. Under normal circumstances, checksetup.pl will
+ # automatically re-create these dropped FKs at the end of its DB upgrade
+ # run, so we don't need to re-create them in this method.
+ my @columns = $self->bz_table_columns($old_name);
+ foreach my $column (@columns) {
+
+ # these just return silently if there's no FK to drop
+ $self->bz_drop_fk($old_name, $column);
+ $self->bz_drop_related_fks($old_name, $column);
+ }
+
+ my @sql = $self->_bz_real_schema->get_rename_table_sql($old_name, $new_name);
+ print get_text('install_table_rename', {old => $old_name, new => $new_name})
+ . "\n"
+ if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
+ $self->do($_) foreach @sql;
+ $self->_bz_real_schema->rename_table($old_name, $new_name);
+ $self->_bz_store_real_schema;
}
sub bz_set_next_serial_value {
- my ($self, $table, $column, $value) = @_;
- if (!$value) {
- $value = $self->selectrow_array("SELECT MAX($column) FROM $table") || 0;
- $value++;
- }
- my @sql = $self->_bz_real_schema->get_set_serial_sql($table, $column, $value);
- $self->do($_) foreach @sql;
+ my ($self, $table, $column, $value) = @_;
+ if (!$value) {
+ $value = $self->selectrow_array("SELECT MAX($column) FROM $table") || 0;
+ $value++;
+ }
+ my @sql = $self->_bz_real_schema->get_set_serial_sql($table, $column, $value);
+ $self->do($_) foreach @sql;
}
#####################################################################
@@ -1129,12 +1153,12 @@ sub bz_set_next_serial_value {
#####################################################################
sub _bz_schema {
- my ($self) = @_;
- return $self->{private_bz_schema} if exists $self->{private_bz_schema};
- my @module_parts = split('::', ref $self);
- my $module_name = pop @module_parts;
- $self->{private_bz_schema} = Bugzilla::DB::Schema->new($module_name);
- return $self->{private_bz_schema};
+ my ($self) = @_;
+ return $self->{private_bz_schema} if exists $self->{private_bz_schema};
+ my @module_parts = split('::', ref $self);
+ my $module_name = pop @module_parts;
+ $self->{private_bz_schema} = Bugzilla::DB::Schema->new($module_name);
+ return $self->{private_bz_schema};
}
# _bz_get_initial_schema()
@@ -1148,53 +1172,54 @@ sub _bz_schema {
# 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();
+ my ($self) = @_;
+ return $self->_bz_schema->get_empty_schema();
}
sub bz_column_info {
- my ($self, $table, $column) = @_;
- my $def = $self->_bz_real_schema->get_column_abstract($table, $column);
- # We dclone it so callers can't modify the Schema.
- $def = dclone($def) if defined $def;
- return $def;
+ my ($self, $table, $column) = @_;
+ my $def = $self->_bz_real_schema->get_column_abstract($table, $column);
+
+ # We dclone it so callers can't modify the Schema.
+ $def = dclone($def) if defined $def;
+ return $def;
}
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;
+ 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);
+ 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);
+ my ($self, $table) = @_;
+ return $self->_bz_real_schema->get_table_columns($table);
}
sub bz_table_indexes {
- my ($self, $table) = @_;
- my $indexes = $self->_bz_real_schema->get_table_indexes_abstract($table);
- my %return_indexes;
- # We do this so that they're always hashes.
- foreach my $name (keys %$indexes) {
- $return_indexes{$name} = $self->bz_index_info($table, $name);
- }
- return \%return_indexes;
+ my ($self, $table) = @_;
+ my $indexes = $self->_bz_real_schema->get_table_indexes_abstract($table);
+ my %return_indexes;
+
+ # We do this so that they're always hashes.
+ foreach my $name (keys %$indexes) {
+ $return_indexes{$name} = $self->bz_index_info($table, $name);
+ }
+ return \%return_indexes;
}
sub bz_table_list {
- my ($self) = @_;
- return $self->_bz_real_schema->get_table_list();
+ my ($self) = @_;
+ return $self->_bz_real_schema->get_table_list();
}
#####################################################################
@@ -1213,9 +1238,9 @@ sub bz_table_list {
# 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]}) };
+ my ($self, $table) = @_;
+ my $sth = $self->column_info(undef, undef, $table, '%');
+ return @{$self->selectcol_arrayref($sth, {Columns => [4]})};
}
# bz_table_list_real()
@@ -1225,9 +1250,9 @@ sub bz_table_columns_real {
# 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] })};
+ my ($self) = @_;
+ my $table_sth = $self->table_info(undef, undef, undef, "TABLE");
+ return @{$self->selectcol_arrayref($table_sth, {Columns => [3]})};
}
#####################################################################
@@ -1235,54 +1260,58 @@ sub bz_table_list_real {
#####################################################################
sub bz_in_transaction {
- return $_[0]->{private_bz_transaction_count} ? 1 : 0;
+ return $_[0]->{private_bz_transaction_count} ? 1 : 0;
}
sub bz_start_transaction {
- my ($self) = @_;
-
- if ($self->bz_in_transaction) {
- $self->{private_bz_transaction_count}++;
- } else {
- # Turn AutoCommit off and start a new transaction
- $self->begin_work();
- # REPEATABLE READ means "We work on a snapshot of the DB that
- # is created when we execute our first SQL statement." It's
- # what we need in Bugzilla to be safe, for what we do.
- # Different DBs have different defaults for their isolation
- # level, so we just set it here manually.
- if ($self->ISOLATION_LEVEL) {
- $self->do('SET TRANSACTION ISOLATION LEVEL '
- . $self->ISOLATION_LEVEL);
- }
- $self->{private_bz_transaction_count} = 1;
+ my ($self) = @_;
+
+ if ($self->bz_in_transaction) {
+ $self->{private_bz_transaction_count}++;
+ }
+ else {
+ # Turn AutoCommit off and start a new transaction
+ $self->begin_work();
+
+ # REPEATABLE READ means "We work on a snapshot of the DB that
+ # is created when we execute our first SQL statement." It's
+ # what we need in Bugzilla to be safe, for what we do.
+ # Different DBs have different defaults for their isolation
+ # level, so we just set it here manually.
+ if ($self->ISOLATION_LEVEL) {
+ $self->do('SET TRANSACTION ISOLATION LEVEL ' . $self->ISOLATION_LEVEL);
}
+ $self->{private_bz_transaction_count} = 1;
+ }
}
sub bz_commit_transaction {
- my ($self) = @_;
-
- if ($self->{private_bz_transaction_count} > 1) {
- $self->{private_bz_transaction_count}--;
- } elsif ($self->bz_in_transaction) {
- $self->commit();
- $self->{private_bz_transaction_count} = 0;
- } else {
- ThrowCodeError('not_in_transaction');
- }
+ my ($self) = @_;
+
+ if ($self->{private_bz_transaction_count} > 1) {
+ $self->{private_bz_transaction_count}--;
+ }
+ elsif ($self->bz_in_transaction) {
+ $self->commit();
+ $self->{private_bz_transaction_count} = 0;
+ }
+ else {
+ ThrowCodeError('not_in_transaction');
+ }
}
sub bz_rollback_transaction {
- my ($self) = @_;
-
- # Unlike start and commit, if we rollback at any point it happens
- # instantly, even if we're in a nested transaction.
- if (!$self->bz_in_transaction) {
- ThrowCodeError("not_in_transaction");
- } else {
- $self->rollback();
- $self->{private_bz_transaction_count} = 0;
- }
+ my ($self) = @_;
+
+ # Unlike start and commit, if we rollback at any point it happens
+ # instantly, even if we're in a nested transaction.
+ if (!$self->bz_in_transaction) {
+ ThrowCodeError("not_in_transaction");
+ }
+ else {
+ $self->rollback();
+ $self->{private_bz_transaction_count} = 0;
+ }
}
#####################################################################
@@ -1290,43 +1319,45 @@ sub bz_rollback_transaction {
#####################################################################
sub _build_connector {
- my ($self) = @_;
- my ($dsn, $user, $pass, $override_attrs) =
- map { $self->$_ } qw(dsn user pass attrs);
-
- # set up default attributes used to connect to the database
- # (may be overridden by DB driver implementations)
- my $attributes = { RaiseError => 1,
- 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 ($override_attrs) {
- foreach my $key (keys %$override_attrs) {
- $attributes->{$key} = $override_attrs->{$key};
- }
+ my ($self) = @_;
+ my ($dsn, $user, $pass, $override_attrs)
+ = map { $self->$_ } qw(dsn user pass attrs);
+
+ # set up default attributes used to connect to the database
+ # (may be overridden by DB driver implementations)
+ my $attributes = {
+ RaiseError => 1,
+ 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 ($override_attrs) {
+ foreach my $key (keys %$override_attrs) {
+ $attributes->{$key} = $override_attrs->{$key};
}
- my $class = ref $self;
- weaken($self);
- $attributes->{Callbacks} = {
- connected => sub {
- my ($dbh, $dsn) = @_;
- INFO("$PROGRAM_NAME connected mysql $dsn");
- ThrowCodeError('not_in_transaction') if $self && $self->bz_in_transaction;
- $class->on_dbi_connected(@_) if $class->can('on_dbi_connected');
- return
- },
- };
-
- return DBIx::Connector->new($dsn, $user, $pass, $attributes);
+ }
+ my $class = ref $self;
+ weaken($self);
+ $attributes->{Callbacks} = {
+ connected => sub {
+ my ($dbh, $dsn) = @_;
+ INFO("$PROGRAM_NAME connected mysql $dsn");
+ ThrowCodeError('not_in_transaction') if $self && $self->bz_in_transaction;
+ $class->on_dbi_connected(@_) if $class->can('on_dbi_connected');
+ return;
+ },
+ };
+
+ return DBIx::Connector->new($dsn, $user, $pass, $attributes);
}
#####################################################################
@@ -1350,55 +1381,54 @@ These methods really are private. Do not override them in subclasses.
=cut
sub _bz_init_schema_storage {
- my ($self) = @_;
-
- my $table_size;
- eval {
- $table_size =
- $self->selectrow_array("SELECT COUNT(*) FROM bz_schema");
- };
+ 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');
+ }
- 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 install_string('db_schema_init'), "\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();
- print install_string('db_schema_init'), "\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).";
+ # 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()>
@@ -1412,24 +1442,23 @@ sub _bz_init_schema_storage {
=cut
sub _bz_real_schema {
- my ($self) = @_;
- return $self->{private_real_schema} if exists $self->{private_real_schema};
-
- my $bz_schema;
- unless ($bz_schema = Bugzilla->memcached->get({ key => 'bz_schema' })) {
- $bz_schema = $self->selectrow_arrayref(
- "SELECT schema_data, version FROM bz_schema"
- );
- Bugzilla->memcached->set({ key => 'bz_schema', value => $bz_schema });
- }
+ my ($self) = @_;
+ return $self->{private_real_schema} if exists $self->{private_real_schema};
- (die "_bz_real_schema tried to read the bz_schema table but it's empty!")
- if !$bz_schema;
+ my $bz_schema;
+ unless ($bz_schema = Bugzilla->memcached->get({key => 'bz_schema'})) {
+ $bz_schema
+ = $self->selectrow_arrayref("SELECT schema_data, version FROM bz_schema");
+ Bugzilla->memcached->set({key => 'bz_schema', value => $bz_schema});
+ }
- $self->{private_real_schema} =
- $self->_bz_schema->deserialize_abstract($bz_schema->[0], $bz_schema->[1]);
+ (die "_bz_real_schema tried to read the bz_schema table but it's empty!")
+ if !$bz_schema;
- return $self->{private_real_schema};
+ $self->{private_real_schema}
+ = $self->_bz_schema->deserialize_abstract($bz_schema->[0], $bz_schema->[1]);
+
+ return $self->{private_real_schema};
}
=item C<_bz_store_real_schema()>
@@ -1449,106 +1478,135 @@ sub _bz_real_schema {
=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();
+ my ($self) = @_;
+
+ # Make sure that there's a schema to update
+ my $table_size = $self->selectrow_array("SELECT COUNT(*) FROM bz_schema");
- Bugzilla->memcached->clear({ key => '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();
+
+ Bugzilla->memcached->clear({key => 'bz_schema'});
}
# For bz_populate_enum_tables
sub _bz_populate_enum_table {
- my ($self, $table, $valuelist) = @_;
-
- my $sql_table = $self->quote_identifier($table);
-
- # Check if there are any table entries
- my $table_size = $self->selectrow_array("SELECT COUNT(*) FROM $sql_table");
-
- # If the table is empty...
- if (!$table_size) {
- print " $table";
- my $insert = $self->prepare(
- "INSERT INTO $sql_table (value,sortkey) VALUES (?,?)");
- my $sortorder = 0;
- my $maxlen = max(map(length($_), @$valuelist)) + 2;
- foreach my $value (@$valuelist) {
- $sortorder += 100;
- $insert->execute($value, $sortorder);
- }
+ my ($self, $table, $valuelist) = @_;
+
+ my $sql_table = $self->quote_identifier($table);
+
+ # Check if there are any table entries
+ my $table_size = $self->selectrow_array("SELECT COUNT(*) FROM $sql_table");
+
+ # If the table is empty...
+ if (!$table_size) {
+ print " $table";
+ my $insert
+ = $self->prepare("INSERT INTO $sql_table (value,sortkey) VALUES (?,?)");
+ my $sortorder = 0;
+ my $maxlen = max(map(length($_), @$valuelist)) + 2;
+ foreach my $value (@$valuelist) {
+ $sortorder += 100;
+ $insert->execute($value, $sortorder);
}
+ }
}
# This is used before adding a foreign key to a column, to make sure
# that the database won't fail adding the key.
sub _check_references {
- my ($self, $table, $column, $fk) = @_;
- my $foreign_table = $fk->{TABLE};
- my $foreign_column = $fk->{COLUMN};
-
- # We use table aliases because sometimes we join a table to itself,
- # and we can't use the same table name on both sides of the join.
- # We also can't use the words "table" or "foreign" because those are
- # reserved words.
- my $bad_values = $self->selectcol_arrayref(
- "SELECT DISTINCT tabl.$column
+ my ($self, $table, $column, $fk) = @_;
+ my $foreign_table = $fk->{TABLE};
+ my $foreign_column = $fk->{COLUMN};
+
+ # We use table aliases because sometimes we join a table to itself,
+ # and we can't use the same table name on both sides of the join.
+ # We also can't use the words "table" or "foreign" because those are
+ # reserved words.
+ my $bad_values = $self->selectcol_arrayref(
+ "SELECT DISTINCT tabl.$column
FROM $table AS tabl LEFT JOIN $foreign_table AS forn
ON tabl.$column = forn.$foreign_column
WHERE forn.$foreign_column IS NULL
- AND tabl.$column IS NOT NULL");
-
- if (@$bad_values) {
- my $delete_action = $fk->{DELETE} || '';
- if ($delete_action eq 'CASCADE') {
- $self->do("DELETE FROM $table WHERE $column IN ("
- . join(',', ('?') x @$bad_values) . ")",
- undef, @$bad_values);
- if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
- print "\n", get_text('install_fk_invalid_fixed',
- { table => $table, column => $column,
- foreign_table => $foreign_table,
- foreign_column => $foreign_column,
- 'values' => $bad_values, action => 'delete' }), "\n";
- }
- }
- elsif ($delete_action eq 'SET NULL') {
- $self->do("UPDATE $table SET $column = NULL
+ AND tabl.$column IS NOT NULL"
+ );
+
+ if (@$bad_values) {
+ my $delete_action = $fk->{DELETE} || '';
+ if ($delete_action eq 'CASCADE') {
+ $self->do(
+ "DELETE FROM $table WHERE $column IN (" . join(',', ('?') x @$bad_values) . ")",
+ undef, @$bad_values
+ );
+ if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
+ print "\n",
+ get_text(
+ 'install_fk_invalid_fixed',
+ {
+ table => $table,
+ column => $column,
+ foreign_table => $foreign_table,
+ foreign_column => $foreign_column,
+ 'values' => $bad_values,
+ action => 'delete'
+ }
+ ),
+ "\n";
+ }
+ }
+ elsif ($delete_action eq 'SET NULL') {
+ $self->do(
+ "UPDATE $table SET $column = NULL
WHERE $column IN ("
- . join(',', ('?') x @$bad_values) . ")",
- undef, @$bad_values);
- if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
- print "\n", get_text('install_fk_invalid_fixed',
- { table => $table, column => $column,
- foreign_table => $foreign_table,
- foreign_column => $foreign_column,
- 'values' => $bad_values, action => 'null' }), "\n";
- }
- }
- else {
- die "\n", get_text('install_fk_invalid',
- { table => $table, column => $column,
- foreign_table => $foreign_table,
- foreign_column => $foreign_column,
- 'values' => $bad_values }), "\n";
+ . join(',', ('?') x @$bad_values) . ")", undef, @$bad_values
+ );
+ if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
+ print "\n",
+ get_text(
+ 'install_fk_invalid_fixed',
+ {
+ table => $table,
+ column => $column,
+ foreign_table => $foreign_table,
+ foreign_column => $foreign_column,
+ 'values' => $bad_values,
+ action => 'null'
+ }
+ ),
+ "\n";
+ }
+ }
+ else {
+ die "\n",
+ get_text(
+ 'install_fk_invalid',
+ {
+ table => $table,
+ column => $column,
+ foreign_table => $foreign_table,
+ foreign_column => $foreign_column,
+ 'values' => $bad_values
}
+ ),
+ "\n";
}
+ }
}
1;