Commit 40a19977 authored by mkanat%bugzilla.org's avatar mkanat%bugzilla.org

Bug 373442: Add referential integrity against the profiles table in some more simple places

Patch By Max Kanat-Alexander <mkanat@bugzilla.org> (module owner) a=mkanat
parent 2884cd86
...@@ -43,6 +43,7 @@ use Bugzilla::Error; ...@@ -43,6 +43,7 @@ use Bugzilla::Error;
use Bugzilla::DB::Schema; use Bugzilla::DB::Schema;
use List::Util qw(max); use List::Util qw(max);
use Storable qw(dclone);
##################################################################### #####################################################################
# Constants # Constants
...@@ -428,6 +429,23 @@ sub bz_populate_enum_tables { ...@@ -428,6 +429,23 @@ sub bz_populate_enum_tables {
} }
} }
sub bz_setup_foreign_keys {
my ($self) = @_;
# We use _bz_schema because bz_add_table has removed all REFERENCES
# items from _bz_real_schema.
my @tables = $self->_bz_schema->get_table_list();
foreach my $table (@tables) {
my @columns = $self->_bz_schema->get_table_columns($table);
foreach my $column (@columns) {
my $def = $self->_bz_schema->get_column_abstract($table, $column);
if ($def->{REFERENCES}) {
$self->bz_add_fk($table, $column, $def->{REFERENCES});
}
}
}
}
##################################################################### #####################################################################
# Schema Modification Methods # Schema Modification Methods
##################################################################### #####################################################################
...@@ -463,6 +481,24 @@ sub bz_add_column { ...@@ -463,6 +481,24 @@ sub bz_add_column {
} }
} }
sub bz_add_fk {
my ($self, $table, $column, $def) = @_;
my $col_def = $self->bz_column_info($table, $column);
if (!$col_def->{REFERENCES}) {
$self->_check_references($table, $column, $def->{TABLE},
$def->{COLUMN});
print get_text('install_fk_add',
{ table => $table, column => $column, fk => $def })
. "\n" if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
my @sql = $self->_bz_real_schema->get_add_fk_sql($table, $column, $def);
$self->do($_) foreach @sql;
$col_def->{REFERENCES} = $def;
$self->_bz_real_schema->set_column($table, $column, $col_def);
$self->_bz_store_real_schema;
}
}
sub bz_alter_column { sub bz_alter_column {
my ($self, $table, $name, $new_def, $set_nulls_to) = @_; my ($self, $table, $name, $new_def, $set_nulls_to) = @_;
...@@ -515,11 +551,10 @@ sub bz_alter_column_raw { ...@@ -515,11 +551,10 @@ sub bz_alter_column_raw {
my @statements = $self->_bz_real_schema->get_alter_column_ddl( my @statements = $self->_bz_real_schema->get_alter_column_ddl(
$table, $name, $new_def, $table, $name, $new_def,
defined $set_nulls_to ? $self->quote($set_nulls_to) : undef); defined $set_nulls_to ? $self->quote($set_nulls_to) : undef);
my $new_ddl = $self->_bz_schema->get_display_ddl($table, $name, $new_def); my $new_ddl = $self->_bz_schema->get_type_ddl($new_def);
print "Updating column $name in table $table ...\n"; print "Updating column $name in table $table ...\n";
if (defined $current_def) { if (defined $current_def) {
my $old_ddl = $self->_bz_schema->get_display_ddl($table, $name, my $old_ddl = $self->_bz_schema->get_type_ddl($current_def);
$current_def);
print "Old: $old_ddl\n"; print "Old: $old_ddl\n";
} }
print "New: $new_ddl\n"; print "New: $new_ddl\n";
...@@ -569,8 +604,17 @@ sub bz_add_table { ...@@ -569,8 +604,17 @@ sub bz_add_table {
if (!$table_exists) { if (!$table_exists) {
$self->_bz_add_table_raw($name); $self->_bz_add_table_raw($name);
$self->_bz_real_schema->add_table($name, my $table_def = dclone($self->_bz_schema->get_table_abstract($name));
$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.
delete $fields{$col}->{REFERENCES};
}
$self->_bz_real_schema->add_table($name, $table_def);
$self->_bz_store_real_schema; $self->_bz_store_real_schema;
} }
} }
...@@ -751,7 +795,10 @@ sub _bz_get_initial_schema { ...@@ -751,7 +795,10 @@ sub _bz_get_initial_schema {
sub bz_column_info { sub bz_column_info {
my ($self, $table, $column) = @_; my ($self, $table, $column) = @_;
return $self->_bz_real_schema->get_column_abstract($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 { sub bz_index_info {
...@@ -1050,6 +1097,39 @@ sub _bz_populate_enum_table { ...@@ -1050,6 +1097,39 @@ sub _bz_populate_enum_table {
} }
} }
# 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, $foreign_table, $foreign_column) = @_;
my $bad_values = $self->selectcol_arrayref(
"SELECT DISTINCT $table.$column
FROM $table LEFT JOIN $foreign_table
ON $table.$column = $foreign_table.$foreign_column
WHERE $foreign_table.$foreign_column IS NULL
AND $table.$column IS NOT NULL");
if (@$bad_values) {
my $values = join(', ', @$bad_values);
print <<EOT;
ERROR: There are invalid values for the $column column in the $table
table. (These values do not exist in the $foreign_table table, in the
$foreign_column column.)
Before continuing with checksetup, you will need to fix these values,
either by deleting these rows from the database, or changing the values
of $column in $table to point to valid values in $foreign_table.$foreign_column.
The bad values from the $table.$column column are:
$values
EOT
# I just picked a number above 2, to be considered "abnormal exit."
exit 3;
}
}
1; 1;
__END__ __END__
......
...@@ -40,6 +40,7 @@ use Bugzilla::Hook; ...@@ -40,6 +40,7 @@ use Bugzilla::Hook;
use Bugzilla::Util; use Bugzilla::Util;
use Bugzilla::Constants; use Bugzilla::Constants;
use Carp qw(confess);
use Hash::Util qw(lock_value unlock_hash lock_keys unlock_keys); use Hash::Util qw(lock_value unlock_hash lock_keys unlock_keys);
use Safe; use Safe;
# Historical, needed for SCHEMA_VERSION = '1.00' # Historical, needed for SCHEMA_VERSION = '1.00'
...@@ -98,20 +99,6 @@ more about how this integrates into the rest of Bugzilla. ...@@ -98,20 +99,6 @@ more about how this integrates into the rest of Bugzilla.
=over =over
=item C<TABLES_FIRST>
Because of L</"Referential Integrity">, certain tables must be created
before other tables. L</ABSTRACT_SCHEMA> can't specify this, because
it's a hash. So we specify it here. When calling C<get_table_list()>,
these tables will be returned before the other tables.
=cut
use constant TABLES_FIRST => qw(
fielddefs
profiles
);
=item C<SCHEMA_VERSION> =item C<SCHEMA_VERSION>
The 'version' of the internal schema structure. This version number The 'version' of the internal schema structure. This version number
...@@ -194,7 +181,7 @@ The column pointed at in that table. ...@@ -194,7 +181,7 @@ The column pointed at in that table.
=item C<DELETE> =item C<DELETE>
What to do if the row in the parent table is deleted. Choices are What to do if the row in the parent table is deleted. Choices are
C<RESTRICT> or C<CASCADE>. C<RESTRICT>, C<CASCADE>, or C<SET NULL>.
C<RESTRICT> means the deletion of the row in the parent table will C<RESTRICT> means the deletion of the row in the parent table will
be forbidden by the database if there is a row in I<this> table that be forbidden by the database if there is a row in I<this> table that
...@@ -203,17 +190,19 @@ C<DELETE>. ...@@ -203,17 +190,19 @@ C<DELETE>.
C<CASCADE> means that this row will be deleted along with that row. C<CASCADE> means that this row will be deleted along with that row.
C<SET NULL> means that the column will be set to C<NULL> when the parent
row is deleted. Note that this is only valid if the column can actually
be set to C<NULL>. (That is, the column isn't C<NOT NULL>.)
=item C<UPDATE> =item C<UPDATE>
What to do if the value in the parent table is updated. It has the What to do if the value in the parent table is updated. You can set this
same choices as L</DELETE>, except it defaults to C<CASCADE>, which means to C<CASCADE> or C<RESTRICT>, which mean the same thing as they do for
"also update this column in this table." L</DELETE>. This variable defaults to C<CASCADE>, which means "also
update this column in this table."
=back =back
Note that not all our supported databases actually enforce C<RESTRICT>
and C<CASCADE>, so don't depend on them.
=cut =cut
use constant SCHEMA_VERSION => '2.00'; use constant SCHEMA_VERSION => '2.00';
...@@ -293,7 +282,9 @@ use constant ABSTRACT_SCHEMA => { ...@@ -293,7 +282,9 @@ use constant ABSTRACT_SCHEMA => {
FIELDS => [ FIELDS => [
bug_id => {TYPE => 'INT3', NOTNULL => 1}, bug_id => {TYPE => 'INT3', NOTNULL => 1},
attach_id => {TYPE => 'INT3'}, attach_id => {TYPE => 'INT3'},
who => {TYPE => 'INT3', NOTNULL => 1}, who => {TYPE => 'INT3', NOTNULL => 1,
REFERENCES => {TABLE => 'profiles',
COLUMN => 'userid'}},
bug_when => {TYPE => 'DATETIME', NOTNULL => 1}, bug_when => {TYPE => 'DATETIME', NOTNULL => 1},
fieldid => {TYPE => 'INT3', NOTNULL => 1}, fieldid => {TYPE => 'INT3', NOTNULL => 1},
added => {TYPE => 'TINYTEXT'}, added => {TYPE => 'TINYTEXT'},
...@@ -310,7 +301,10 @@ use constant ABSTRACT_SCHEMA => { ...@@ -310,7 +301,10 @@ use constant ABSTRACT_SCHEMA => {
cc => { cc => {
FIELDS => [ FIELDS => [
bug_id => {TYPE => 'INT3', NOTNULL => 1}, bug_id => {TYPE => 'INT3', NOTNULL => 1},
who => {TYPE => 'INT3', NOTNULL => 1}, who => {TYPE => 'INT3', NOTNULL => 1,
REFERENCES => {TABLE => 'profiles',
COLUMN => 'userid',
DELETE => 'CASCADE'}},
], ],
INDEXES => [ INDEXES => [
cc_bug_id_idx => {FIELDS => [qw(bug_id who)], cc_bug_id_idx => {FIELDS => [qw(bug_id who)],
...@@ -359,7 +353,10 @@ use constant ABSTRACT_SCHEMA => { ...@@ -359,7 +353,10 @@ use constant ABSTRACT_SCHEMA => {
votes => { votes => {
FIELDS => [ FIELDS => [
who => {TYPE => 'INT3', NOTNULL => 1}, who => {TYPE => 'INT3', NOTNULL => 1,
REFERENCES => {TABLE => 'profiles',
COLUMN => 'userid',
DELETE => 'CASCADE'}},
bug_id => {TYPE => 'INT3', NOTNULL => 1}, bug_id => {TYPE => 'INT3', NOTNULL => 1},
vote_count => {TYPE => 'INT2', NOTNULL => 1}, vote_count => {TYPE => 'INT2', NOTNULL => 1},
], ],
...@@ -379,7 +376,9 @@ use constant ABSTRACT_SCHEMA => { ...@@ -379,7 +376,9 @@ use constant ABSTRACT_SCHEMA => {
mimetype => {TYPE => 'MEDIUMTEXT', NOTNULL => 1}, mimetype => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
ispatch => {TYPE => 'BOOLEAN'}, ispatch => {TYPE => 'BOOLEAN'},
filename => {TYPE => 'varchar(100)', NOTNULL => 1}, filename => {TYPE => 'varchar(100)', NOTNULL => 1},
submitter_id => {TYPE => 'INT3', NOTNULL => 1}, submitter_id => {TYPE => 'INT3', NOTNULL => 1,
REFERENCES => {TABLE => 'profiles',
COLUMN => 'userid'}},
isobsolete => {TYPE => 'BOOLEAN', NOTNULL => 1, isobsolete => {TYPE => 'BOOLEAN', NOTNULL => 1,
DEFAULT => 'FALSE'}, DEFAULT => 'FALSE'},
isprivate => {TYPE => 'BOOLEAN', NOTNULL => 1, isprivate => {TYPE => 'BOOLEAN', NOTNULL => 1,
...@@ -725,7 +724,10 @@ use constant ABSTRACT_SCHEMA => { ...@@ -725,7 +724,10 @@ use constant ABSTRACT_SCHEMA => {
email_setting => { email_setting => {
FIELDS => [ FIELDS => [
user_id => {TYPE => 'INT3', NOTNULL => 1}, user_id => {TYPE => 'INT3', NOTNULL => 1,
REFERENCES => {TABLE => 'profiles',
COLUMN => 'userid',
DELETE => 'CASCADE'}},
relationship => {TYPE => 'INT1', NOTNULL => 1}, relationship => {TYPE => 'INT1', NOTNULL => 1},
event => {TYPE => 'INT1', NOTNULL => 1}, event => {TYPE => 'INT1', NOTNULL => 1},
], ],
...@@ -738,8 +740,14 @@ use constant ABSTRACT_SCHEMA => { ...@@ -738,8 +740,14 @@ use constant ABSTRACT_SCHEMA => {
watch => { watch => {
FIELDS => [ FIELDS => [
watcher => {TYPE => 'INT3', NOTNULL => 1}, watcher => {TYPE => 'INT3', NOTNULL => 1,
watched => {TYPE => 'INT3', NOTNULL => 1}, REFERENCES => {TABLE => 'profiles',
COLUMN => 'userid',
DELETE => 'CASCADE'}},
watched => {TYPE => 'INT3', NOTNULL => 1,
REFERENCES => {TABLE => 'profiles',
COLUMN => 'userid',
DELETE => 'CASCADE'}},
], ],
INDEXES => [ INDEXES => [
watch_watcher_idx => {FIELDS => [qw(watcher watched)], watch_watcher_idx => {FIELDS => [qw(watcher watched)],
...@@ -752,7 +760,10 @@ use constant ABSTRACT_SCHEMA => { ...@@ -752,7 +760,10 @@ use constant ABSTRACT_SCHEMA => {
FIELDS => [ FIELDS => [
id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
PRIMARYKEY => 1}, PRIMARYKEY => 1},
userid => {TYPE => 'INT3', NOTNULL => 1}, userid => {TYPE => 'INT3', NOTNULL => 1,
REFERENCES => {TABLE => 'profiles',
COLUMN => 'userid',
DELETE => 'CASCADE'}},
name => {TYPE => 'varchar(64)', NOTNULL => 1}, name => {TYPE => 'varchar(64)', NOTNULL => 1},
query => {TYPE => 'MEDIUMTEXT', NOTNULL => 1}, query => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
query_type => {TYPE => 'BOOLEAN', NOTNULL => 1}, query_type => {TYPE => 'BOOLEAN', NOTNULL => 1},
...@@ -765,8 +776,14 @@ use constant ABSTRACT_SCHEMA => { ...@@ -765,8 +776,14 @@ use constant ABSTRACT_SCHEMA => {
namedqueries_link_in_footer => { namedqueries_link_in_footer => {
FIELDS => [ FIELDS => [
namedquery_id => {TYPE => 'INT3', NOTNULL => 1}, namedquery_id => {TYPE => 'INT3', NOTNULL => 1,
user_id => {TYPE => 'INT3', NOTNULL => 1}, REFERENCES => {TABLE => 'namedqueries',
COLUMN => 'id',
DELETE => 'CASCADE'}},
user_id => {TYPE => 'INT3', NOTNULL => 1,
REFERENCES => {TABLE => 'profiles',
COLUMN => 'userid',
DELETE => 'CASCADE'}},
], ],
INDEXES => [ INDEXES => [
namedqueries_link_in_footer_id_idx => {FIELDS => [qw(namedquery_id user_id)], namedqueries_link_in_footer_id_idx => {FIELDS => [qw(namedquery_id user_id)],
...@@ -778,7 +795,10 @@ use constant ABSTRACT_SCHEMA => { ...@@ -778,7 +795,10 @@ use constant ABSTRACT_SCHEMA => {
component_cc => { component_cc => {
FIELDS => [ FIELDS => [
user_id => {TYPE => 'INT3', NOTNULL => 1}, user_id => {TYPE => 'INT3', NOTNULL => 1,
REFERENCES => {TABLE => 'profiles',
COLUMN => 'userid',
DELETE => 'CASCADE'}},
component_id => {TYPE => 'INT2', NOTNULL => 1}, component_id => {TYPE => 'INT2', NOTNULL => 1},
], ],
INDEXES => [ INDEXES => [
...@@ -794,7 +814,10 @@ use constant ABSTRACT_SCHEMA => { ...@@ -794,7 +814,10 @@ use constant ABSTRACT_SCHEMA => {
FIELDS => [ FIELDS => [
cookie => {TYPE => 'varchar(16)', NOTNULL => 1, cookie => {TYPE => 'varchar(16)', NOTNULL => 1,
PRIMARYKEY => 1}, PRIMARYKEY => 1},
userid => {TYPE => 'INT3', NOTNULL => 1}, userid => {TYPE => 'INT3', NOTNULL => 1,
REFERENCES => {TABLE => 'profiles',
COLUMN => 'userid',
DELETE => 'CASCADE'}},
ipaddr => {TYPE => 'varchar(40)', NOTNULL => 1}, ipaddr => {TYPE => 'varchar(40)', NOTNULL => 1},
lastused => {TYPE => 'DATETIME', NOTNULL => 1}, lastused => {TYPE => 'DATETIME', NOTNULL => 1},
], ],
...@@ -808,7 +831,9 @@ use constant ABSTRACT_SCHEMA => { ...@@ -808,7 +831,9 @@ use constant ABSTRACT_SCHEMA => {
# for these changes. # for these changes.
tokens => { tokens => {
FIELDS => [ FIELDS => [
userid => {TYPE => 'INT3'}, userid => {TYPE => 'INT3', REFERENCES => {TABLE => 'profiles',
COLUMN => 'userid',
DELETE => 'CASCADE'}},
issuedate => {TYPE => 'DATETIME', NOTNULL => 1} , issuedate => {TYPE => 'DATETIME', NOTNULL => 1} ,
token => {TYPE => 'varchar(16)', NOTNULL => 1, token => {TYPE => 'varchar(16)', NOTNULL => 1,
PRIMARYKEY => 1}, PRIMARYKEY => 1},
...@@ -996,8 +1021,13 @@ use constant ABSTRACT_SCHEMA => { ...@@ -996,8 +1021,13 @@ use constant ABSTRACT_SCHEMA => {
PRIMARYKEY => 1}, PRIMARYKEY => 1},
name => {TYPE => 'varchar(64)', NOTNULL => 1}, name => {TYPE => 'varchar(64)', NOTNULL => 1},
product_id => {TYPE => 'INT2', NOTNULL => 1}, product_id => {TYPE => 'INT2', NOTNULL => 1},
initialowner => {TYPE => 'INT3', NOTNULL => 1}, initialowner => {TYPE => 'INT3', NOTNULL => 1,
initialqacontact => {TYPE => 'INT3'}, REFERENCES => {TABLE => 'profiles',
COLUMN => 'userid'}},
initialqacontact => {TYPE => 'INT3',
REFERENCES => {TABLE => 'profiles',
COLUMN => 'userid',
DELETE => 'SET NULL'}},
description => {TYPE => 'MEDIUMTEXT', NOTNULL => 1}, description => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
], ],
INDEXES => [ INDEXES => [
...@@ -1063,7 +1093,10 @@ use constant ABSTRACT_SCHEMA => { ...@@ -1063,7 +1093,10 @@ use constant ABSTRACT_SCHEMA => {
FIELDS => [ FIELDS => [
id => {TYPE => 'MEDIUMSERIAL', PRIMARYKEY => 1, id => {TYPE => 'MEDIUMSERIAL', PRIMARYKEY => 1,
NOTNULL => 1}, NOTNULL => 1},
eventid => {TYPE => 'INT3', NOTNULL => 1}, eventid => {TYPE => 'INT3', NOTNULL => 1,
REFERENCES => {TABLE => 'whine_events',
COLUMN => 'id',
DELETE => 'CASCADE'}},
query_name => {TYPE => 'varchar(64)', NOTNULL => 1, query_name => {TYPE => 'varchar(64)', NOTNULL => 1,
DEFAULT => "''"}, DEFAULT => "''"},
sortkey => {TYPE => 'INT2', NOTNULL => 1, sortkey => {TYPE => 'INT2', NOTNULL => 1,
...@@ -1082,7 +1115,10 @@ use constant ABSTRACT_SCHEMA => { ...@@ -1082,7 +1115,10 @@ use constant ABSTRACT_SCHEMA => {
FIELDS => [ FIELDS => [
id => {TYPE => 'MEDIUMSERIAL', PRIMARYKEY => 1, id => {TYPE => 'MEDIUMSERIAL', PRIMARYKEY => 1,
NOTNULL => 1}, NOTNULL => 1},
eventid => {TYPE => 'INT3', NOTNULL => 1}, eventid => {TYPE => 'INT3', NOTNULL => 1,
REFERENCES => {TABLE => 'whine_events',
COLUMN => 'id',
DELETE => 'CASCADE'}},
run_day => {TYPE => 'varchar(32)'}, run_day => {TYPE => 'varchar(32)'},
run_time => {TYPE => 'varchar(32)'}, run_time => {TYPE => 'varchar(32)'},
run_next => {TYPE => 'DATETIME'}, run_next => {TYPE => 'DATETIME'},
...@@ -1099,7 +1135,10 @@ use constant ABSTRACT_SCHEMA => { ...@@ -1099,7 +1135,10 @@ use constant ABSTRACT_SCHEMA => {
FIELDS => [ FIELDS => [
id => {TYPE => 'MEDIUMSERIAL', PRIMARYKEY => 1, id => {TYPE => 'MEDIUMSERIAL', PRIMARYKEY => 1,
NOTNULL => 1}, NOTNULL => 1},
owner_userid => {TYPE => 'INT3', NOTNULL => 1}, owner_userid => {TYPE => 'INT3', NOTNULL => 1,
REFERENCES => {TABLE => 'profiles',
COLUMN => 'userid',
DELETE => 'CASCADE'}},
subject => {TYPE => 'varchar(128)'}, subject => {TYPE => 'varchar(128)'},
body => {TYPE => 'MEDIUMTEXT'}, body => {TYPE => 'MEDIUMTEXT'},
], ],
...@@ -1159,7 +1198,10 @@ use constant ABSTRACT_SCHEMA => { ...@@ -1159,7 +1198,10 @@ use constant ABSTRACT_SCHEMA => {
profile_setting => { profile_setting => {
FIELDS => [ FIELDS => [
user_id => {TYPE => 'INT3', NOTNULL => 1}, user_id => {TYPE => 'INT3', NOTNULL => 1,
REFERENCES => {TABLE => 'profiles',
COLUMN => 'userid',
DELETE => 'CASCADE'}},
setting_name => {TYPE => 'varchar(32)', NOTNULL => 1}, setting_name => {TYPE => 'varchar(32)', NOTNULL => 1},
setting_value => {TYPE => 'varchar(32)', NOTNULL => 1}, setting_value => {TYPE => 'varchar(32)', NOTNULL => 1},
], ],
...@@ -1376,7 +1418,8 @@ C<ALTER TABLE> SQL statement ...@@ -1376,7 +1418,8 @@ C<ALTER TABLE> SQL statement
my $self = shift; my $self = shift;
my $finfo = (@_ == 1 && ref($_[0]) eq 'HASH') ? $_[0] : { @_ }; my $finfo = (@_ == 1 && ref($_[0]) eq 'HASH') ? $_[0] : { @_ };
my $type = $finfo->{TYPE}; my $type = $finfo->{TYPE};
die "A valid TYPE was not specified for this column." unless ($type); confess "A valid TYPE was not specified for this column (got "
. Dumper($finfo) . ")" unless ($type);
my $default = $finfo->{DEFAULT}; my $default = $finfo->{DEFAULT};
# Replace any abstract default value (such as 'TRUE' or 'FALSE') # Replace any abstract default value (such as 'TRUE' or 'FALSE')
...@@ -1397,17 +1440,6 @@ C<ALTER TABLE> SQL statement ...@@ -1397,17 +1440,6 @@ C<ALTER TABLE> SQL statement
} #eosub--get_type_ddl } #eosub--get_type_ddl
# Used if you want to display the DDL to the user, as opposed to actually
# use it in the database.
sub get_display_ddl {
my ($self, $table, $column, $def) = @_;
my $ddl = $self->get_type_ddl($def);
if ($def->{REFERENCES}) {
$ddl .= $self->get_fk_ddl($table, $column, $def->{REFERENCES});
}
return $ddl;
}
sub get_fk_ddl { sub get_fk_ddl {
=item C<_get_fk_ddl> =item C<_get_fk_ddl>
...@@ -1441,8 +1473,8 @@ is undefined. ...@@ -1441,8 +1473,8 @@ is undefined.
my $update = $references->{UPDATE} || 'CASCADE'; my $update = $references->{UPDATE} || 'CASCADE';
my $delete = $references->{DELETE} || 'RESTRICT'; my $delete = $references->{DELETE} || 'RESTRICT';
my $to_table = $references->{TABLE} || die "No table in reference"; my $to_table = $references->{TABLE} || confess "No table in reference";
my $to_column = $references->{COLUMN} || die "No column in reference"; my $to_column = $references->{COLUMN} || confess "No column in reference";
my $fk_name = $self->_get_fk_name($table, $column, $references); my $fk_name = $self->_get_fk_name($table, $column, $references);
return "\n CONSTRAINT $fk_name FOREIGN KEY ($column)\n" return "\n CONSTRAINT $fk_name FOREIGN KEY ($column)\n"
...@@ -1460,10 +1492,10 @@ sub _get_fk_name { ...@@ -1460,10 +1492,10 @@ sub _get_fk_name {
return "fk_${table}_${column}_${to_table}_${to_column}"; return "fk_${table}_${column}_${to_table}_${to_column}";
} }
sub _get_add_fk_sql { sub get_add_fk_sql {
my ($self, $table, $column, $new_def) = @_; my ($self, $table, $column, $def) = @_;
my $fk_string = $self->get_fk_ddl($table, $column, $new_def->{REFERENCES}); my $fk_string = $self->get_fk_ddl($table, $column, $def);
return ("ALTER TABLE $table ADD $fk_string"); return ("ALTER TABLE $table ADD $fk_string");
} }
...@@ -1519,22 +1551,12 @@ sub get_table_list { ...@@ -1519,22 +1551,12 @@ sub get_table_list {
Parameters: none Parameters: none
Returns: An array of table names. The tables specified Returns: An array of table names, in alphabetical order.
in L</TABLES_FIRST> will come first, in order. The
rest of the tables will be in random order.
=cut =cut
my $self = shift; my $self = shift;
return sort keys %{$self->{schema}};
my %schema = %{$self->{schema}};
my @tables;
foreach my $table (TABLES_FIRST) {
push(@tables, $table);
delete $schema{$table};
}
push(@tables, keys %schema);
return @tables;
} }
sub get_table_columns { sub get_table_columns {
...@@ -1603,14 +1625,6 @@ sub get_table_ddl { ...@@ -1603,14 +1625,6 @@ sub get_table_ddl {
push(@ddl, $index_sql) if $index_sql; push(@ddl, $index_sql) if $index_sql;
} }
my %fields = @{$self->{schema}{$table}{FIELDS}};
foreach my $col (keys %fields) {
my $def = $fields{$col};
if ($def->{REFERENCES}) {
push(@ddl, $self->_get_add_fk_sql($table, $col, $def));
}
}
push(@ddl, @{ $self->{schema}{$table}{DB_EXTRAS} }) push(@ddl, @{ $self->{schema}{$table}{DB_EXTRAS} })
if (ref($self->{schema}{$table}{DB_EXTRAS})); if (ref($self->{schema}{$table}{DB_EXTRAS}));
...@@ -1704,6 +1718,11 @@ sub get_add_column_ddl { ...@@ -1704,6 +1718,11 @@ sub get_add_column_ddl {
(push(@statements, "UPDATE $table SET $column = $init_value")) (push(@statements, "UPDATE $table SET $column = $init_value"))
if defined $init_value; if defined $init_value;
if (defined $definition->{REFERENCES}) {
push(@statements, $self->get_add_fk_sql($table, $column,
$definition->{REFERENCES}));
}
return (@statements); return (@statements);
} }
...@@ -1824,15 +1843,6 @@ sub get_alter_column_ddl { ...@@ -1824,15 +1843,6 @@ sub get_alter_column_ddl {
push(@statements, "ALTER TABLE $table DROP PRIMARY KEY"); push(@statements, "ALTER TABLE $table DROP PRIMARY KEY");
} }
# If we went from not having an FK to having an FK
# XXX For right now, you can't change an FK's actual definition.
if (!$old_def->{REFERENCES} && $new_def->{REFERENCES}) {
push(@statements, $self->_get_add_fk_sql($table, $column, $new_def));
}
elsif ($old_def->{REFERENCES} && !$new_def->{REFERENCES}) {
push(@statements, $self->_get_drop_fk_sql($table, $column, $old_def));
}
return @statements; return @statements;
} }
...@@ -2220,31 +2230,17 @@ sub columns_equal { ...@@ -2220,31 +2230,17 @@ sub columns_equal {
$col_one->{TYPE} = uc($col_one->{TYPE}); $col_one->{TYPE} = uc($col_one->{TYPE});
$col_two->{TYPE} = uc($col_two->{TYPE}); $col_two->{TYPE} = uc($col_two->{TYPE});
# It doesn't work to compare the two REFERENCES items, because # We don't care about foreign keys when comparing column definitions.
# they look like 'HASH(0xaf3c434)' to diff_arrays--they'll never delete $col_one->{REFERENCES};
# be equal. delete $col_two->{REFERENCES};
my %col_one_def = %$col_one;
delete $col_one_def{REFERENCES};
my %col_two_def = %$col_two;
delete $col_two_def{REFERENCES};
my @col_one_array = %col_one_def; my @col_one_array = %$col_one;
my @col_two_array = %col_two_def; my @col_two_array = %$col_two;
my ($removed, $added) = diff_arrays(\@col_one_array, \@col_two_array); my ($removed, $added) = diff_arrays(\@col_one_array, \@col_two_array);
# If there are no differences between the arrays, then they are equal. # If there are no differences between the arrays, then they are equal.
my $defs_identical = !scalar(@$removed) && !scalar(@$added); return !scalar(@$removed) && !scalar(@$added) ? 1 : 0;
# If the basic definitions are identical, we still want to check
# if the two foreign key definitions are different.
my @fk_array_one = %{$col_one->{REFERENCES} || {}};
my @fk_array_two = %{$col_two->{REFERENCES} || {}};
my ($fk_removed, $fk_added) =
diff_arrays(\@fk_array_one, \@fk_array_two);
my $fk_identical = !scalar(@$fk_removed) && !scalar(@$fk_added);
return $defs_identical && $fk_identical ? 1 : 0;
} }
......
...@@ -176,17 +176,10 @@ sub get_alter_column_ddl { ...@@ -176,17 +176,10 @@ sub get_alter_column_ddl {
# keys are not allowed. # keys are not allowed.
delete $new_def_copy{PRIMARYKEY}; delete $new_def_copy{PRIMARYKEY};
} }
# CHANGE COLUMN doesn't support REFERENCES
delete $new_def_copy{REFERENCES};
my $new_ddl = $self->get_type_ddl(\%new_def_copy); my $new_ddl = $self->get_type_ddl(\%new_def_copy);
my @statements; my @statements;
# Drop the FK if the new definition doesn't have one.
if ($old_def->{REFERENCES} && !$new_def->{REFERENCES}) {
push(@statements, $self->_get_drop_fk_sql($table, $column, $old_def));
}
push(@statements, "UPDATE $table SET $column = $set_nulls_to push(@statements, "UPDATE $table SET $column = $set_nulls_to
WHERE $column IS NULL") if defined $set_nulls_to; WHERE $column IS NULL") if defined $set_nulls_to;
push(@statements, "ALTER TABLE $table CHANGE COLUMN push(@statements, "ALTER TABLE $table CHANGE COLUMN
...@@ -196,10 +189,6 @@ sub get_alter_column_ddl { ...@@ -196,10 +189,6 @@ sub get_alter_column_ddl {
push(@statements, "ALTER TABLE $table DROP PRIMARY KEY"); push(@statements, "ALTER TABLE $table DROP PRIMARY KEY");
} }
# Add the FK if the new definition has one and the old definition doesn't.
if ($new_def->{REFERENCES} && !$old_def->{REFERENCES}) {
push(@statements, $self->_get_add_fk_sql($table, $column, $new_def));
}
return @statements; return @statements;
} }
......
...@@ -49,39 +49,6 @@ sub indicate_progress { ...@@ -49,39 +49,6 @@ sub indicate_progress {
} }
} }
# 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 ($table, $column, $foreign_table, $foreign_column) = @_;
my $dbh = Bugzilla->dbh;
my $bad_values = $dbh->selectcol_arrayref(
"SELECT DISTINCT $table.$column
FROM $table LEFT JOIN $foreign_table
ON $table.$column = $foreign_table.$foreign_column
WHERE $foreign_table.$foreign_column IS NULL");
if (@$bad_values) {
my $values = join(', ', @$bad_values);
print <<EOT;
ERROR: There are invalid values for the $column column in the $table
table. (These values do not exist in the $foreign_table table, in the
$foreign_column column.)
Before continuing with checksetup, you will need to fix these values,
either by deleting these rows from the database, or changing the values
of $column in $table to point to valid values in $foreign_table.$foreign_column.
The bad values from the $table.$column column are:
$values
EOT
# I just picked a number above 2, to be considered "abnormal exit."
exit 3;
}
}
# NOTE: This is NOT the function for general table updates. See # NOTE: This is NOT the function for general table updates. See
# update_table_definitions for that. This is only for the fielddefs table. # update_table_definitions for that. This is only for the fielddefs table.
sub update_fielddefs_definition { sub update_fielddefs_definition {
...@@ -413,9 +380,9 @@ sub update_table_definitions { ...@@ -413,9 +380,9 @@ sub update_table_definitions {
if ($dbh->bz_column_info('components', 'initialqacontact')->{NOTNULL}) { if ($dbh->bz_column_info('components', 'initialqacontact')->{NOTNULL}) {
$dbh->bz_alter_column('components', 'initialqacontact', $dbh->bz_alter_column('components', 'initialqacontact',
{TYPE => 'INT3'}); {TYPE => 'INT3'});
}
$dbh->do("UPDATE components SET initialqacontact = NULL " . $dbh->do("UPDATE components SET initialqacontact = NULL " .
"WHERE initialqacontact = 0"); "WHERE initialqacontact = 0");
}
_migrate_email_prefs_to_new_table(); _migrate_email_prefs_to_new_table();
_initialize_dependency_tree_changes_email_pref(); _initialize_dependency_tree_changes_email_pref();
...@@ -552,25 +519,13 @@ sub update_table_definitions { ...@@ -552,25 +519,13 @@ sub update_table_definitions {
$dbh->bz_add_column('milestones', 'id', $dbh->bz_add_column('milestones', 'id',
{TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
# Referential Integrity begins here
check_references('profiles_activity', 'userid', 'profiles', 'userid');
$dbh->bz_alter_column('profiles_activity', 'userid',
{TYPE => 'INT3', NOTNULL => 1, REFERENCES =>
{TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'}});
check_references('profiles_activity', 'who', 'profiles', 'userid');
$dbh->bz_alter_column('profiles_activity', 'who',
{TYPE => 'INT3', NOTNULL => 1, REFERENCES =>
{TABLE => 'profiles', COLUMN => 'userid'}});
check_references('profiles_activity', 'fieldid', 'fielddefs', 'id');
$dbh->bz_alter_column('profiles_activity', 'fieldid',
{TYPE => 'INT3', NOTNULL => 1, REFERENCES =>
{TABLE => 'fielddefs', COLUMN => 'id'}});
################################################################ ################################################################
# New --TABLE-- changes should go *** A B O V E *** this point # # New --TABLE-- changes should go *** A B O V E *** this point #
################################################################ ################################################################
Bugzilla::Hook::process('install-update_db'); Bugzilla::Hook::process('install-update_db');
$dbh->bz_setup_foreign_keys();
} }
# Subroutines should be ordered in the order that they are called. # Subroutines should be ordered in the order that they are called.
......
...@@ -382,6 +382,9 @@ ...@@ -382,6 +382,9 @@
[% ELSIF message_tag == "install_file_perms_fix" %] [% ELSIF message_tag == "install_file_perms_fix" %]
Fixing file permissions... Fixing file permissions...
[% ELSIF message_tag == "install_fk_add" %]
Adding foreign key: [% table FILTER html %].[% column FILTER html %] -&gt; [% fk.TABLE FILTER html %].[% fk.COLUMN FILTER html %]...
[% ELSIF message_tag == "install_group_create" %] [% ELSIF message_tag == "install_group_create" %]
Creating group [% name FILTER html %]... Creating group [% name FILTER html %]...
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment