Commit 0ac4d26f authored by Max Kanat-Alexander's avatar Max Kanat-Alexander

Bug 569312: Speed up the adding of many FKs to the same table for MySQL

and PostgreSQL, by adding them all in one ALTER statement r=mkanat, a=mkanat (module owner)
parent cef81246
...@@ -447,12 +447,14 @@ sub bz_setup_foreign_keys { ...@@ -447,12 +447,14 @@ sub bz_setup_foreign_keys {
my @tables = $self->_bz_schema->get_table_list(); my @tables = $self->_bz_schema->get_table_list();
foreach my $table (@tables) { foreach my $table (@tables) {
my @columns = $self->_bz_schema->get_table_columns($table); my @columns = $self->_bz_schema->get_table_columns($table);
my %add_fks;
foreach my $column (@columns) { foreach my $column (@columns) {
my $def = $self->_bz_schema->get_column_abstract($table, $column); my $def = $self->_bz_schema->get_column_abstract($table, $column);
if ($def->{REFERENCES}) { if ($def->{REFERENCES}) {
$self->bz_add_fk($table, $column, $def->{REFERENCES}); $add_fks{$column} = $def->{REFERENCES};
} }
} }
$self->bz_add_fks($table, \%add_fks);
} }
} }
...@@ -506,19 +508,36 @@ sub bz_add_column { ...@@ -506,19 +508,36 @@ sub bz_add_column {
sub bz_add_fk { sub bz_add_fk {
my ($self, $table, $column, $def) = @_; my ($self, $table, $column, $def) = @_;
$self->bz_add_fks($table, { $column => $def });
}
my $col_def = $self->bz_column_info($table, $column); sub bz_add_fks {
if (!$col_def->{REFERENCES}) { my ($self, $table, $column_fks) = @_;
$self->_check_references($table, $column, $def);
my %add_these;
foreach my $column (keys %$column_fks) {
my $col_def = $self->bz_column_info($table, $column);
next if $col_def->{REFERENCES};
my $fk = $column_fks->{$column};
$self->_check_references($table, $column, $fk);
$add_these{$column} = $fk;
print get_text('install_fk_add', print get_text('install_fk_add',
{ table => $table, column => $column, fk => $def }) { table => $table, column => $column, fk => $fk })
. "\n" if Bugzilla->usage_mode == USAGE_MODE_CMDLINE; . "\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; return if !scalar(keys %add_these);
my @sql = $self->_bz_real_schema->get_add_fks_sql($table, \%add_these);
$self->do($_) foreach @sql;
foreach my $column (keys %add_these) {
my $col_def = $self->bz_column_info($table, $column);
$col_def->{REFERENCES} = $add_these{$column};
$self->_bz_real_schema->set_column($table, $column, $col_def); $self->_bz_real_schema->set_column($table, $column, $col_def);
$self->_bz_store_real_schema;
} }
$self->_bz_store_real_schema();
} }
sub bz_alter_column { sub bz_alter_column {
...@@ -700,11 +719,11 @@ sub bz_add_field_tables { ...@@ -700,11 +719,11 @@ sub bz_add_field_tables {
$self->_bz_add_field_table($ms_table, $self->_bz_add_field_table($ms_table,
$self->_bz_schema->MULTI_SELECT_VALUE_TABLE); $self->_bz_schema->MULTI_SELECT_VALUE_TABLE);
$self->bz_add_fk($ms_table, 'bug_id', {TABLE => 'bugs', $self->bz_add_fks($ms_table,
COLUMN => 'bug_id', { bug_id => {TABLE => 'bugs', COLUMN => 'bug_id',
DELETE => 'CASCADE'}); DELETE => 'CASCADE'},
$self->bz_add_fk($ms_table, 'value', {TABLE => $field->name,
COLUMN => 'value'}); value => {TABLE => $field->name, COLUMN => 'value'} });
} }
} }
......
...@@ -210,6 +210,9 @@ update this column in this table." ...@@ -210,6 +210,9 @@ update this column in this table."
use constant SCHEMA_VERSION => '2.00'; use constant SCHEMA_VERSION => '2.00';
use constant ADD_COLUMN => 'ADD COLUMN'; use constant ADD_COLUMN => 'ADD COLUMN';
# Multiple FKs can be added using ALTER TABLE ADD CONSTRAINT in one
# SQL statement. This isn't true for all databases.
use constant MULTIPLE_FKS_IN_ALTER => 1;
# This is a reasonable default that's true for both PostgreSQL and MySQL. # This is a reasonable default that's true for both PostgreSQL and MySQL.
use constant MAX_IDENTIFIER_LEN => 63; use constant MAX_IDENTIFIER_LEN => 63;
...@@ -1817,11 +1820,26 @@ sub _hash_identifier { ...@@ -1817,11 +1820,26 @@ sub _hash_identifier {
} }
sub get_add_fk_sql { sub get_add_fks_sql {
my ($self, $table, $column, $def) = @_; my ($self, $table, $column_fks) = @_;
my $fk_string = $self->get_fk_ddl($table, $column, $def); my @add;
return ("ALTER TABLE $table ADD $fk_string"); foreach my $column (keys %$column_fks) {
my $def = $column_fks->{$column};
my $fk_string = $self->get_fk_ddl($table, $column, $def);
push(@add, $fk_string);
}
my @sql;
if ($self->MULTIPLE_FKS_IN_ALTER) {
my $alter = "ALTER TABLE $table ADD " . join(', ADD ', @add);
push(@sql, $alter);
}
else {
foreach my $fk_string (@add) {
push(@sql, "ALTER TABLE $table ADD $fk_string");
}
}
return @sql;
} }
sub get_drop_fk_sql { sub get_drop_fk_sql {
......
...@@ -35,6 +35,7 @@ use Carp qw(confess); ...@@ -35,6 +35,7 @@ use Carp qw(confess);
use Bugzilla::Util; use Bugzilla::Util;
use constant ADD_COLUMN => 'ADD'; use constant ADD_COLUMN => 'ADD';
use constant MULTIPLE_FKS_IN_ALTER => 0;
# Whether this is true or not, this is what it needs to be in order for # Whether this is true or not, this is what it needs to be in order for
# hash_identifier to maintain backwards compatibility with versions before # hash_identifier to maintain backwards compatibility with versions before
# 3.2rc2. # 3.2rc2.
...@@ -136,40 +137,44 @@ sub get_drop_index_ddl { ...@@ -136,40 +137,44 @@ sub get_drop_index_ddl {
# - Delete CASCADE # - Delete CASCADE
# - Delete SET NULL # - Delete SET NULL
sub get_fk_ddl { sub get_fk_ddl {
my ($self, $table, $column, $references) = @_; my $self = shift;
return "" if !$references; my $ddl = $self->SUPER::get_fk_ddl(@_);
my $update = $references->{UPDATE} || 'CASCADE'; # iThe Bugzilla Oracle driver implements UPDATE via a trigger.
my $delete = $references->{DELETE}; $ddl =~ s/ON UPDATE \S+//i;
my $to_table = $references->{TABLE} || confess "No table in reference"; # RESTRICT is the default for DELETE on Oracle and may not be specified.
my $to_column = $references->{COLUMN} || confess "No column in reference"; $ddl =~ s/ON DELETE RESTRICT//i;
my $fk_name = $self->_get_fk_name($table, $column, $references);
# 'ON DELETE RESTRICT' is enabled by default return $ddl;
$delete = "" if ( defined $delete && $delete =~ /RESTRICT/i); }
my $fk_string = "\n CONSTRAINT $fk_name FOREIGN KEY ($column)\n" sub get_add_fks_sql {
. " REFERENCES $to_table($to_column)\n"; my $self = shift;
my ($table, $column_fks) = @_;
$fk_string = $fk_string . " ON DELETE $delete" if $delete; my @sql = $self->SUPER::get_add_fks_sql(@_);
if ( $update =~ /CASCADE/i ){ foreach my $column (keys %$column_fks) {
my $tr_str = "CREATE OR REPLACE TRIGGER ${fk_name}_UC" my $fk = $column_fks->{$column};
. " AFTER UPDATE OF $to_column ON $to_table " next if $fk->{UPDATE} && uc($fk->{UPDATE}) ne 'CASCADE';
. " REFERENCING " my $fk_name = $self->_get_fk_name($table, $column, $fk);
. " NEW AS NEW " my $to_column = $fk->{COLUMN};
. " OLD AS OLD " my $to_table = $fk->{TABLE};
. " FOR EACH ROW "
. " BEGIN " my $trigger = <<END;
. " UPDATE $table" CREATE OR REPLACE TRIGGER ${fk_name}_UC
. " SET $column = :NEW.$to_column" AFTER UPDATE OF $to_column ON $to_table
. " WHERE $column = :OLD.$to_column;" REFERENCING NEW AS NEW OLD AS OLD
. " END ${fk_name}_UC;"; FOR EACH ROW
my $dbh = Bugzilla->dbh; BEGIN
$dbh->do($tr_str); UPDATE $table
SET $column = :NEW.$to_column
WHERE $column = :OLD.$to_column;
END ${fk_name}_UC;
END
push(@sql, $trigger);
} }
return $fk_string; return @sql;
} }
sub get_drop_fk_sql { sub get_drop_fk_sql {
......
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