mirror of
https://github.com/percona/percona-toolkit.git
synced 2025-09-10 21:19:59 +00:00
Change --update-foreign-keys-method to --alter-foreign-keys-method and make it required if there are child tables. Add 'auto' and 'none' (this one yet implemented) as methods. Fix drop_swap when it's auto-chosen. Test that the expected method is used. Exit 1 instead of 0 if user didn't specify required options.
This commit is contained in:
@@ -4936,10 +4936,10 @@ sub main {
|
||||
$tbl = $dsn->{t};
|
||||
}
|
||||
|
||||
my $update_fk_method = $o->get('update-foreign-keys-method') || '';
|
||||
if ( $update_fk_method eq 'drop_swap' ) {
|
||||
my $alter_fk_method = $o->get('alter-foreign-keys-method') || '';
|
||||
if ( $alter_fk_method =~ m/^(?:drop_swap|none)/ ) {
|
||||
$o->set('swap-tables', 0);
|
||||
$o->set('drop-old-table', 0),
|
||||
$o->set('drop-old-table', 0);
|
||||
}
|
||||
|
||||
# Explicit --chunk-size disable auto chunk sizing.
|
||||
@@ -4964,11 +4964,13 @@ sub main {
|
||||
|
||||
# See the "pod-based-option-value-validation" spec for how this may
|
||||
# be automagically validated.
|
||||
if ( $update_fk_method
|
||||
&& $update_fk_method ne 'rebuild_constraints'
|
||||
&& $update_fk_method ne 'drop_swap' )
|
||||
if ( $alter_fk_method
|
||||
&& $alter_fk_method ne 'auto'
|
||||
&& $alter_fk_method ne 'rebuild_constraints'
|
||||
&& $alter_fk_method ne 'drop_swap'
|
||||
&& $alter_fk_method ne 'none' )
|
||||
{
|
||||
$o->save_error("Invalid --update-foreign-keys-method value: $update_fk_method");
|
||||
$o->save_error("Invalid --alter-foreign-keys-method value: $alter_fk_method");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5233,18 +5235,37 @@ sub main {
|
||||
Cxn => $cxn,
|
||||
Quoter => $q,
|
||||
);
|
||||
if ( $o->got('update-foreign-keys-method') && ! @$child_tables ) {
|
||||
warn "No foreign keys reference the table; ignoring --update-foreign-keys-method\n";
|
||||
if ( !$child_tables ) {
|
||||
if ( $alter_fk_method ) {
|
||||
warn "No foreign keys reference the table; ignoring "
|
||||
. "--alter-foreign-keys-method.\n";
|
||||
}
|
||||
# No child tables and --alter-fk-method wasn't specified,
|
||||
# so nothing to do.
|
||||
}
|
||||
else {
|
||||
print "Child tables:\n";
|
||||
print " $_->{name}\n" for @$child_tables;
|
||||
foreach my $child_table ( @$child_tables ) {
|
||||
printf " %s (approx. %s rows)\n",
|
||||
$child_table->{name},
|
||||
$child_table->{row_est} || '?';
|
||||
}
|
||||
|
||||
# Let the user know how we're going to update the child table fk refs.
|
||||
print "Will "
|
||||
. ($update_fk_method ? "use the $update_fk_method"
|
||||
: "automatically determine the")
|
||||
. " method to update child table foreign keys.\n";
|
||||
if ( $alter_fk_method ) {
|
||||
# Let the user know how we're going to update the child table fk refs.
|
||||
my $choice
|
||||
= $alter_fk_method eq 'none' ? "not"
|
||||
: $alter_fk_method eq 'auto' ? "automatically choose the method to"
|
||||
: "use the $alter_fk_method method to";
|
||||
print "Will $choice update child table foreign keys.\n";
|
||||
}
|
||||
else {
|
||||
print "Exiting without altering $orig_tbl->{name} because there "
|
||||
. "are child tables but --alter-foreign-keys-method was not "
|
||||
. "specified. Please read the tool's documentation carefully "
|
||||
. "and specify --alter-foreign-keys-method.\n";
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
# ########################################################################
|
||||
@@ -5266,7 +5287,7 @@ sub main {
|
||||
print "Exiting without altering $orig_tbl->{name} because neither "
|
||||
. "--dry-run nor --execute was specified. Please read the tool's "
|
||||
. "documentation carefully before using this tool.\n";
|
||||
return $exit_status;
|
||||
return 1;
|
||||
}
|
||||
|
||||
# ########################################################################
|
||||
@@ -5788,6 +5809,37 @@ sub main {
|
||||
}
|
||||
$orig_tbl->{copied} = 1; # flag for cleanup tasks
|
||||
|
||||
|
||||
# XXX Auto-choose the alter fk method BEFORE swapping/renaming tables
|
||||
# else everything will break because if drop_swap is chosen, then we
|
||||
# most NOT rename tables or drop the old table.
|
||||
if ( $alter_fk_method eq 'auto' ) {
|
||||
# If chunk time is set, then use the average rate of rows/s
|
||||
# from copying the orig table to determine the max size of
|
||||
# a child table that can be altered within one chunk time.
|
||||
# The limit is a fudge factor. Chunk time won't be set if
|
||||
# the user specified --chunk-size=N on the cmd line, in which
|
||||
# case the max child table size is their specified chunk size
|
||||
# times the fudge factor.
|
||||
my $max_rows
|
||||
= $o->get('dry-run') ? $o->get('chunk-size') * $limit
|
||||
: $chunk_time ? $avg_rate * $chunk_time * $limit
|
||||
: $o->get('chunk-size') * $limit;
|
||||
PTDEBUG && _d('Max allowed child table size:', $max_rows);
|
||||
|
||||
$alter_fk_method = determine_alter_fk_method(
|
||||
child_tables => $child_tables,
|
||||
max_rows => $max_rows,
|
||||
Cxn => $cxn,
|
||||
OptionParser => $o,
|
||||
);
|
||||
|
||||
if ( $alter_fk_method eq 'drop_swap' ) {
|
||||
$o->set('swap-tables', 0);
|
||||
$o->set('drop-old-table', 0);
|
||||
}
|
||||
}
|
||||
|
||||
# #####################################################################
|
||||
# XXX
|
||||
# Step 5: Rename tables: orig -> old, new -> orig
|
||||
@@ -5823,27 +5875,12 @@ sub main {
|
||||
# #####################################################################
|
||||
if ( $child_tables ) {
|
||||
eval {
|
||||
if ( !$update_fk_method ) {
|
||||
# If chunk time is set, then use the average rate of rows/s
|
||||
# from copying the orig table to determine the max size of
|
||||
# a child table that can be altered within one chunk time.
|
||||
# The limit is a fudge factor. Chunk time won't be set if
|
||||
# the user specified --chunk-size=N on the cmd line, in which
|
||||
# case the max child table size is their specified chunk size
|
||||
# times the fudge factor.
|
||||
my $max_rows = $o->get('dry-run') ? $o->get('chunk-size') * $limit
|
||||
: $chunk_time ? $avg_rate * $chunk_time * $limit
|
||||
: $o->get('chunk-size') * $limit;
|
||||
PTDEBUG && _d('Max allowed child table size:', $max_rows);
|
||||
$update_fk_method = determine_update_fk_method(
|
||||
child_tables => $child_tables,
|
||||
max_rows => $max_rows,
|
||||
Cxn => $cxn,
|
||||
OptionParser => $o,
|
||||
);
|
||||
if ( $alter_fk_method eq 'none' ) {
|
||||
print "Not updating child table foreign keys because "
|
||||
. "--alter-foreign-keys-method=none. Child table foreign "
|
||||
. "keys will be broken when the tool finishes.\n";
|
||||
}
|
||||
|
||||
if ( ($update_fk_method || '') eq 'rebuild_constraints' ) {
|
||||
elsif ( $alter_fk_method eq 'rebuild_constraints' ) {
|
||||
rebuild_constraints(
|
||||
orig_tbl => $orig_tbl,
|
||||
old_tbl => $old_tbl,
|
||||
@@ -5854,7 +5891,7 @@ sub main {
|
||||
TableParser => $tp,
|
||||
);
|
||||
}
|
||||
elsif ( ($update_fk_method || '') eq 'drop_swap' ) {
|
||||
elsif ( $alter_fk_method eq 'drop_swap' ) {
|
||||
drop_swap(
|
||||
orig_tbl => $orig_tbl,
|
||||
new_tbl => $new_tbl,
|
||||
@@ -5862,6 +5899,10 @@ sub main {
|
||||
OptionParser => $o,
|
||||
);
|
||||
}
|
||||
else {
|
||||
# This should "never" happen because we check this var earlier.
|
||||
die "Invalid --alter-foreign-keys-method: $alter_fk_method\n";
|
||||
}
|
||||
};
|
||||
if ( $EVAL_ERROR ) {
|
||||
# TODO: improve error message and handling.
|
||||
@@ -6106,25 +6147,37 @@ sub find_child_tables {
|
||||
. "WHERE constraint_schema='$tbl->{db}' "
|
||||
. "AND referenced_table_name='$tbl->{tbl}'";
|
||||
PTDEBUG && _d($sql);
|
||||
my $child_tables = $cxn->dbh()->selectall_arrayref($sql);
|
||||
if ( !$child_tables || !@$child_tables ) {
|
||||
my $rows = $cxn->dbh()->selectall_arrayref($sql);
|
||||
if ( !$rows || !@$rows ) {
|
||||
PTDEBUG && _d('No child tables found');
|
||||
return;
|
||||
}
|
||||
|
||||
my @child_tables = map {
|
||||
my @child_tables;
|
||||
foreach my $row ( @$rows ) {
|
||||
my $tbl = {
|
||||
db => $_->[0],
|
||||
tbl => $_->[1],
|
||||
name => $q->quote(@$_),
|
||||
db => $row->[0],
|
||||
tbl => $row->[1],
|
||||
name => $q->quote(@$row),
|
||||
};
|
||||
$tbl;
|
||||
} @$child_tables;
|
||||
PTDEBUG && _d("Child tables:", @child_tables);
|
||||
|
||||
# Get row estimates for each child table so we can give the user
|
||||
# some input on choosing an --alter-foreign-keys-method if they
|
||||
# don't use "auto".
|
||||
my ($n_rows) = NibbleIterator::get_row_estimate(
|
||||
Cxn => $cxn,
|
||||
tbl => $tbl,
|
||||
);
|
||||
$tbl->{row_est} = $n_rows;
|
||||
|
||||
push @child_tables, $tbl;
|
||||
}
|
||||
|
||||
PTDEBUG && _d('Child tables:', Dumper(\@child_tables));
|
||||
return \@child_tables;
|
||||
}
|
||||
|
||||
sub determine_update_fk_method {
|
||||
sub determine_alter_fk_method {
|
||||
my ( %args ) = @_;
|
||||
my @required_args = qw(child_tables max_rows Cxn OptionParser);
|
||||
foreach my $arg ( @required_args ) {
|
||||
@@ -6135,7 +6188,7 @@ sub determine_update_fk_method {
|
||||
if ( $o->get('dry-run') ) {
|
||||
print "Not determining the method to update child table foreign keys "
|
||||
. "because this is a dry run.\n";
|
||||
return;
|
||||
return ''; # $alter_fk_method can't be undef
|
||||
}
|
||||
|
||||
# The rebuild_constraints method is the default becuase it's safer
|
||||
@@ -6160,7 +6213,7 @@ sub determine_update_fk_method {
|
||||
}
|
||||
}
|
||||
|
||||
return $method;
|
||||
return $method || ''; # $alter_fk_method can't be undef
|
||||
}
|
||||
|
||||
sub rebuild_constraints {
|
||||
@@ -6226,10 +6279,10 @@ sub rebuild_constraints {
|
||||
# constraint has the same name as the old one, so we must rename it.
|
||||
# Example: after renaming sakila.actor to sakila.actor_old (for
|
||||
# example), the foreign key on film_actor looks like this:
|
||||
# CONSTRAINT `fk_film_actor_actor` FOREIGN KEY (`actor_id`) REFERENCES
|
||||
# CONSTRAINT `fk_film_actor_actor` FOREIGN KEY (`actor_id`) REFERENCES
|
||||
# `actor_old` (`actor_id`) ON UPDATE CASCADE
|
||||
# We need it to look like this instead:
|
||||
# CONSTRAINT `_fk_film_actor_actor` FOREIGN KEY (`actor_id`) REFERENCES
|
||||
# CONSTRAINT `_fk_film_actor_actor` FOREIGN KEY (`actor_id`) REFERENCES
|
||||
# `actor` (`actor_id`) ON UPDATE CASCADE
|
||||
# Reference the correct table name...
|
||||
$constraint =~ s/REFERENCES[^\(]+/REFERENCES $orig_tbl->{name} /;
|
||||
@@ -6240,6 +6293,7 @@ sub rebuild_constraints {
|
||||
. "ADD $constraint";
|
||||
push @rebuilt_constraints, $sql;
|
||||
}
|
||||
|
||||
my $sql = "ALTER TABLE $child_tbl->{name} "
|
||||
. join(', ', @rebuilt_constraints);
|
||||
print $sql, "\n" if $o->get('print');
|
||||
@@ -6693,7 +6747,7 @@ foreign keys refer to the table. The tool must update foreign keys to refer to
|
||||
the new table after the schema change is complete. The tool supports two methods
|
||||
for accomplishing this, and will automatically choose the appropriate method
|
||||
unless you specify one explicitly. You can read more about this in the
|
||||
documentation for L<"--update-foreign-keys-method">.
|
||||
documentation for L<"--alter-foreign-keys-method">.
|
||||
|
||||
For safety, the tool does not modify the table unless you specify the
|
||||
L<"--execute"> option, which is not enabled by default. The tool supports a
|
||||
@@ -6751,6 +6805,66 @@ to the MySQL manual for the syntax of ALTER TABLE.
|
||||
|
||||
You cannot use the C<RENAME> clause to C<ALTER TABLE>, or the tool will fail.
|
||||
|
||||
=item --alter-foreign-keys-method
|
||||
|
||||
type: string
|
||||
|
||||
How to ensure that foreign keys point to the new table. Foreign keys that
|
||||
reference the table to be altered must be treated specially to ensure that they
|
||||
continue to reference the correct table. When the tool renames the original
|
||||
table to let the new one take its place, by default the foreign keys "follow"
|
||||
the renamed table, and must be changed to reference the new table instead.
|
||||
|
||||
The tool supports two techniques to achieve this. It automatically finds "child
|
||||
tables" that reference the table to be altered, and chooses the best technique
|
||||
automatically, but you may specify one explicitly.
|
||||
|
||||
=over
|
||||
|
||||
=item auto
|
||||
|
||||
Automatically determine whether to use C<rebuild_constraints> or
|
||||
C<drop_swap> based on the size of all child tables compared to the
|
||||
rate of rows copied per second.
|
||||
|
||||
=item rebuild_constraints
|
||||
|
||||
This method uses C<ALTER TABLE> to drop and re-add foreign key constraints that
|
||||
reference the new table. This is the preferred technique, unless one or more of
|
||||
the child tables is so large that the C<ALTER> would take too long. The tool
|
||||
determines that by comparing the number of rows in the child table to the rate
|
||||
at which the tool is able to copy rows from the old table to the new table. If
|
||||
the tool estimates that the child table can be altered in less time than the
|
||||
L<"--chunk-time">, then it will use this technique. For purposes of estimating
|
||||
the time required to alter the child table, the tool multiplies the row-copying
|
||||
rate by L<"--chunk-size-limit">, because MySQL's C<ALTER TABLE> is typically
|
||||
much faster than the external process of copying rows.
|
||||
|
||||
Due to a limitation in MySQL, foreign keys will not have the same names after
|
||||
the ALTER that they did prior to it. The tool has to rename the foreign key when
|
||||
it redefines it, by adding a leading underscore.
|
||||
|
||||
=item drop_swap
|
||||
|
||||
Disable foreign key checks (FOREIGN_KEY_CHECKS=0) then drop the original table
|
||||
before renaming the new table into its place. This is different from the normal
|
||||
method of swapping the old and new table, which uses an atomic C<RENAME> that is
|
||||
undetectable to client applications.
|
||||
|
||||
This method is faster and does not block, but it is riskier for two reasons.
|
||||
First, for a very short time between dropping the original table and renaming
|
||||
the temporary table, the table to be altered simply does not exist, and queries
|
||||
against it will result in an error. Secondly, if there is an error and the new
|
||||
table cannot be renamed into the place of the old one, then it is too late to
|
||||
abort, because the old table is gone permanently.
|
||||
|
||||
=item none
|
||||
|
||||
This method is like C<drop_swap> without the "swap". It leaves child table
|
||||
foreign keys broken.
|
||||
|
||||
=back
|
||||
|
||||
=item --ask-pass
|
||||
|
||||
Prompt for a password when connecting to MySQL.
|
||||
@@ -6845,7 +6959,7 @@ of rows in the chunk. You can disable oversized chunk checking by specifying a
|
||||
value of 0.
|
||||
|
||||
The tool also uses this option to determine how to handle foreign keys that
|
||||
reference the table to be altered. See L<"--update-foreign-keys-method"> for
|
||||
reference the table to be altered. See L<"--alter-foreign-keys-method"> for
|
||||
details.
|
||||
|
||||
=item --chunk-time
|
||||
@@ -7101,55 +7215,6 @@ online schema change process by making the table with the new schema take the
|
||||
place of the original table. The original table becomes the "old table," and
|
||||
the tool drops it unless you disable L<"--[no]drop-old-table">.
|
||||
|
||||
=item --update-foreign-keys-method
|
||||
|
||||
type: string
|
||||
|
||||
How to ensure that foreign keys point to the new table. Foreign keys that
|
||||
reference the table to be altered must be treated specially to ensure that they
|
||||
continue to reference the correct table. When the tool renames the original
|
||||
table to let the new one take its place, by default the foreign keys "follow"
|
||||
the renamed table, and must be changed to reference the new table instead.
|
||||
|
||||
The tool supports two techniques to achieve this. It automatically finds "child
|
||||
tables" that reference the table to be altered, and chooses the best technique
|
||||
automatically, but you may specify one explicitly.
|
||||
|
||||
=over
|
||||
|
||||
=item rebuild_constraints
|
||||
|
||||
This method uses C<ALTER TABLE> to drop and re-add foreign key constraints that
|
||||
reference the new table. This is the preferred technique, unless one or more of
|
||||
the child tables is so large that the C<ALTER> would take too long. The tool
|
||||
determines that by comparing the number of rows in the child table to the rate
|
||||
at which the tool is able to copy rows from the old table to the new table. If
|
||||
the tool estimates that the child table can be altered in less time than the
|
||||
L<"--chunk-time">, then it will use this technique. For purposes of estimating
|
||||
the time required to alter the child table, the tool multiplies the row-copying
|
||||
rate by L<"--chunk-size-limit">, because MySQL's C<ALTER TABLE> is typically
|
||||
much faster than the external process of copying rows.
|
||||
|
||||
Due to a limitation in MySQL, foreign keys will not have the same names after
|
||||
the ALTER that they did prior to it. The tool has to rename the foreign key when
|
||||
it redefines it, by adding a leading underscore.
|
||||
|
||||
=item drop_swap
|
||||
|
||||
Disable foreign key checks (FOREIGN_KEY_CHECKS=0) then drop the original table
|
||||
before renaming the new table into its place. This is different from the normal
|
||||
method of swapping the old and new table, which uses an atomic C<RENAME> that is
|
||||
undetectable to client applications.
|
||||
|
||||
This method is faster and does not block, but it is riskier for two reasons.
|
||||
First, for a very short time between dropping the original table and renaming the
|
||||
temporary table, the table to be altered simply does not exist, and queries
|
||||
against it will result in an error. Secondly, if there is an error and the new
|
||||
table cannot be renamed into the place of the old one, then it is too late to
|
||||
abort, because the old table is gone permanently.
|
||||
|
||||
=back
|
||||
|
||||
=item --user
|
||||
|
||||
short form: -u; type: string
|
||||
|
@@ -32,7 +32,7 @@ elsif ( !$slave_dbh ) {
|
||||
plan skip_all => 'Cannot connect to sandbox slave';
|
||||
}
|
||||
else {
|
||||
plan tests => 55;
|
||||
plan tests => 52;
|
||||
}
|
||||
|
||||
my $q = new Quoter();
|
||||
@@ -52,7 +52,7 @@ $sb->load_file('master', "$sample/basic_no_fks.sql");
|
||||
PerconaTest::wait_for_table($slave_dbh, "pt_osc.t", "id=20");
|
||||
|
||||
$output = output(
|
||||
sub { $exit = pt_online_schema_change::main(@args, "$dsn,t=pt_osc.t",
|
||||
sub { $exit = pt_online_schema_change::main(@args, "$dsn,D=pt_osc,t=t",
|
||||
'--alter', 'drop column id') }
|
||||
);
|
||||
|
||||
@@ -60,7 +60,7 @@ like(
|
||||
$output,
|
||||
qr/neither --dry-run nor --execute was specified/,
|
||||
"Doesn't run without --execute (bug 933232)"
|
||||
);
|
||||
) or warn $output;
|
||||
|
||||
my $ddl = $master_dbh->selectrow_arrayref("show create table pt_osc.t");
|
||||
like(
|
||||
@@ -71,8 +71,8 @@ like(
|
||||
|
||||
is(
|
||||
$exit,
|
||||
0,
|
||||
"Exit 0"
|
||||
1,
|
||||
"Exit 1"
|
||||
);
|
||||
|
||||
# #############################################################################
|
||||
@@ -120,8 +120,9 @@ sub test_alter_table {
|
||||
my $orig_tbls = $master_dbh->selectall_arrayref(
|
||||
"SHOW TABLES FROM `$db`");
|
||||
|
||||
my $fk_method = $args{check_fks};
|
||||
my @orig_fks;
|
||||
if ( $args{check_fks} ) {
|
||||
if ( $fk_method ) {
|
||||
foreach my $tbl ( @$orig_tbls ) {
|
||||
my $fks = $tp->get_fks(
|
||||
$tp->get_create_table($master_dbh, $db, $tbl->[0]));
|
||||
@@ -135,17 +136,20 @@ sub test_alter_table {
|
||||
$output = output(
|
||||
sub { $exit = pt_online_schema_change::main(
|
||||
@args,
|
||||
'--print',
|
||||
"$dsn,D=$db,t=$tbl",
|
||||
@$cmds,
|
||||
)},
|
||||
stderr => 1,
|
||||
);
|
||||
|
||||
my $fail = 0;
|
||||
|
||||
is(
|
||||
$exit,
|
||||
0,
|
||||
"$name exit 0"
|
||||
);
|
||||
) or $fail = 1;
|
||||
|
||||
# There should be no new or missing tables.
|
||||
my $new_tbls = $master_dbh->selectall_arrayref("SHOW TABLES FROM `$db`");
|
||||
@@ -153,7 +157,7 @@ sub test_alter_table {
|
||||
$new_tbls,
|
||||
$orig_tbls,
|
||||
"$name tables"
|
||||
);
|
||||
) or $fail = 1;
|
||||
|
||||
# Rows in the original and new table should be identical.
|
||||
my $new_rows = $master_dbh->selectall_arrayref("SELECT * FROM $table ORDER BY `$pk_col`");
|
||||
@@ -161,7 +165,7 @@ sub test_alter_table {
|
||||
$new_rows,
|
||||
$orig_rows,
|
||||
"$name rows"
|
||||
);
|
||||
) or $fail = 1;
|
||||
|
||||
# Check if the ALTER was actually done.
|
||||
if ( $test_type eq 'drop_col' ) {
|
||||
@@ -172,14 +176,14 @@ sub test_alter_table {
|
||||
$ddl,
|
||||
qr/^\s+$col\s+/m,
|
||||
"$name ALTER DROP COLUMN=$args{drop_col} (dry run)"
|
||||
);
|
||||
) or $fail = 1;
|
||||
}
|
||||
else {
|
||||
unlike(
|
||||
$ddl,
|
||||
qr/^\s+$col\s+/m,
|
||||
"$name ALTER DROP COLUMN=$args{drop_col}"
|
||||
);
|
||||
) or $fail = 1;
|
||||
}
|
||||
}
|
||||
elsif ( $test_type eq 'add_col' ) {
|
||||
@@ -193,22 +197,54 @@ sub test_alter_table {
|
||||
lc($rows->{$tbl}->{engine}),
|
||||
$new_engine,
|
||||
"$name ALTER ENGINE=$args{new_engine}"
|
||||
);
|
||||
) or $fail = 1;
|
||||
|
||||
}
|
||||
|
||||
if ( $args{check_fks} ) {
|
||||
if ( $fk_method ) {
|
||||
my @new_fks;
|
||||
my $rebuild_method = 0;
|
||||
|
||||
foreach my $tbl ( @$orig_tbls ) {
|
||||
my $fks = $tp->get_fks(
|
||||
$tp->get_create_table($master_dbh, $db, $tbl->[0]));
|
||||
push @new_fks, $fks;
|
||||
|
||||
# The tool does not use the same/original fk name,
|
||||
# it appends a single _. So we need to strip this
|
||||
# to compare the original fks to the new fks.
|
||||
if ( $fk_method eq 'rebuild_constraints' ) {
|
||||
my %new_fks = map {
|
||||
my $real_fk_name = $_;
|
||||
my $fk_name = $_;
|
||||
if ( $fk_name =~ s/^_// ) {
|
||||
$rebuild_method = 1;
|
||||
}
|
||||
$fks->{$real_fk_name}->{name} =~ s/^_//;
|
||||
$fks->{$real_fk_name}->{ddl} =~ s/`$real_fk_name`/`$fk_name`/;
|
||||
$fk_name => $fks->{$real_fk_name};
|
||||
} keys %$fks;
|
||||
push @new_fks, \%new_fks;
|
||||
}
|
||||
else {
|
||||
# drop_swap
|
||||
push @new_fks, $fks;
|
||||
}
|
||||
}
|
||||
|
||||
if ( grep { $_ eq '--execute' } @$cmds ) {
|
||||
ok(
|
||||
$fk_method eq 'rebuild_constraints' && $rebuild_method ? 1
|
||||
: $fk_method eq 'drop_swap' && !$rebuild_method ? 1
|
||||
: 0,
|
||||
"$name FK $fk_method method"
|
||||
);
|
||||
}
|
||||
|
||||
is_deeply(
|
||||
\@new_fks,
|
||||
\@orig_fks,
|
||||
"$name FK constraints"
|
||||
);
|
||||
) or $fail = 1;
|
||||
|
||||
# Go that extra mile and verify that the fks are actually
|
||||
# still functiona: i.e. that they'll prevent us from delete
|
||||
@@ -221,7 +257,11 @@ sub test_alter_table {
|
||||
$EVAL_ERROR,
|
||||
qr/foreign key constraint fails/,
|
||||
"$name FK constraints still hold"
|
||||
);
|
||||
) or $fail = 1;
|
||||
}
|
||||
|
||||
if ( $fail ) {
|
||||
diag("Output from failed test:\n$output");
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -281,11 +321,11 @@ test_alter_table(
|
||||
wait => ["pt_osc.address", "address_id=5"],
|
||||
test_type => "drop_col",
|
||||
drop_col => "last_update",
|
||||
check_fks => 1,
|
||||
check_fks => "rebuild_constraints",
|
||||
cmds => [
|
||||
qw(
|
||||
--dry-run
|
||||
--update-foreign-keys-method rebuild_constraints
|
||||
--alter-foreign-keys-method rebuild_constraints
|
||||
),
|
||||
'--alter', 'DROP COLUMN last_update',
|
||||
],
|
||||
@@ -300,11 +340,11 @@ test_alter_table(
|
||||
# wait => ["pt_osc.address", "address_id=5"],
|
||||
test_type => "drop_col",
|
||||
drop_col => "last_update",
|
||||
check_fks => 1,
|
||||
check_fks => "rebuild_constraints",
|
||||
cmds => [
|
||||
qw(
|
||||
--execute
|
||||
--update-foreign-keys-method rebuild_constraints
|
||||
--alter-foreign-keys-method rebuild_constraints
|
||||
),
|
||||
'--alter', 'DROP COLUMN last_update',
|
||||
],
|
||||
@@ -316,25 +356,25 @@ test_alter_table(
|
||||
# Somewhat dangerous, but quick. Downside: table doesn't exist for a moment.
|
||||
|
||||
test_alter_table(
|
||||
name => "Basic FK drop-swap --dry-run",
|
||||
name => "Basic FK drop_swap --dry-run",
|
||||
table => "pt_osc.country",
|
||||
pk_col => "country_id",
|
||||
file => "basic_with_fks.sql",
|
||||
wait => ["pt_osc.address", "address_id=5"],
|
||||
test_type => "drop_col",
|
||||
drop_col => "last_update",
|
||||
check_fks => 1,
|
||||
check_fks => "drop_swap",
|
||||
cmds => [
|
||||
qw(
|
||||
--dry-run
|
||||
--update-foreign-keys-method drop_swap
|
||||
--alter-foreign-keys-method drop_swap
|
||||
),
|
||||
'--alter', 'DROP COLUMN last_update',
|
||||
],
|
||||
);
|
||||
|
||||
test_alter_table(
|
||||
name => "Basic FK drop-swap --execute",
|
||||
name => "Basic FK drop_swap --execute",
|
||||
table => "pt_osc.country",
|
||||
pk_col => "country_id",
|
||||
# The previous test should not have modified the table.
|
||||
@@ -342,48 +382,35 @@ test_alter_table(
|
||||
# wait => ["pt_osc.address", "address_id=5"],
|
||||
test_type => "drop_col",
|
||||
drop_col => "last_update",
|
||||
check_fks => 1,
|
||||
check_fks => "drop_swap",
|
||||
cmds => [
|
||||
qw(
|
||||
--execute
|
||||
--update-foreign-keys-method drop_swap
|
||||
--alter-foreign-keys-method drop_swap
|
||||
),
|
||||
'--alter', 'DROP COLUMN last_update',
|
||||
],
|
||||
);
|
||||
|
||||
# Let the tool auto-determine the fk update method.
|
||||
# Let the tool auto-determine the fk update method. This should choose
|
||||
# the rebuild_constraints method because the tables are quite small.
|
||||
# This is tested by indicating the rebuild_constraints method, which
|
||||
# causes the test sub to verify that the fks have leading _; they won't
|
||||
# if drop_swap was used. To verify this, change auto to drop_swap
|
||||
# and this test will fail.
|
||||
test_alter_table(
|
||||
name => "Basic FK auto --execute",
|
||||
table => "pt_osc.country",
|
||||
pk_col => "country_id",
|
||||
file => "basic_with_fks.sql",
|
||||
wait => ["pt_osc.address", "address_id=5"],
|
||||
test_type => "drop_col",
|
||||
drop_col => "last_update",
|
||||
check_fks => 1,
|
||||
cmds => [
|
||||
name => "Basic FK auto --execute",
|
||||
table => "pt_osc.country",
|
||||
pk_col => "country_id",
|
||||
file => "basic_with_fks.sql",
|
||||
wait => ["pt_osc.address", "address_id=5"],
|
||||
test_type => "drop_col",
|
||||
drop_col => "last_update",
|
||||
check_fks => "rebuild_constraints",
|
||||
cmds => [
|
||||
qw(
|
||||
--execute
|
||||
),
|
||||
'--alter', 'DROP COLUMN last_update',
|
||||
],
|
||||
);
|
||||
|
||||
# Specify the child tables manually.
|
||||
test_alter_table(
|
||||
name => "Basic FK with given child tables",
|
||||
table => "pt_osc.country",
|
||||
pk_col => "country_id",
|
||||
file => "basic_with_fks.sql",
|
||||
wait => ["pt_osc.address", "address_id=5"],
|
||||
test_type => "drop_col",
|
||||
drop_col => "last_update",
|
||||
check_fks => 1,
|
||||
cmds => [
|
||||
qw(
|
||||
--execute
|
||||
--child-tables city
|
||||
--alter-foreign-keys-method auto
|
||||
),
|
||||
'--alter', 'DROP COLUMN last_update',
|
||||
],
|
||||
@@ -404,24 +431,31 @@ sub test_table {
|
||||
|
||||
my $org_rows = $master_dbh->selectall_arrayref('select * from osc.t order by id');
|
||||
|
||||
output(
|
||||
$output = output(
|
||||
sub { $exit = pt_online_schema_change::main(@args,
|
||||
"$dsn,D=osc,t=t", qw(--alter ENGINE=InnoDB)) },
|
||||
"$dsn,D=osc,t=t", qw(--execute --alter ENGINE=InnoDB)) },
|
||||
stderr => 1,
|
||||
);
|
||||
|
||||
my $new_rows = $master_dbh->selectall_arrayref('select * from osc.t order by id');
|
||||
|
||||
my $fail = 0;
|
||||
|
||||
is_deeply(
|
||||
$new_rows,
|
||||
$org_rows,
|
||||
"$name rows"
|
||||
);
|
||||
) or $fail = 1;
|
||||
|
||||
is(
|
||||
$exit,
|
||||
0,
|
||||
"$name exit status 0"
|
||||
);
|
||||
) or $fail = 1;
|
||||
|
||||
if ( $fail ) {
|
||||
diag("Output from failed test:\n$output");
|
||||
}
|
||||
}
|
||||
|
||||
test_table(
|
||||
|
@@ -48,14 +48,14 @@ my $rows;
|
||||
# Of course, the orig database and table must exist.
|
||||
throws_ok(
|
||||
sub { pt_online_schema_change::main(@args,
|
||||
"$dsn,t=nonexistent_db.t", qw(--dry-run)) },
|
||||
qr/`nonexistent_db`.`t` does not exist/,
|
||||
"$dsn,D=nonexistent_db,t=t", qw(--dry-run)) },
|
||||
qr/Unknown database/,
|
||||
"Original database must exist"
|
||||
);
|
||||
|
||||
throws_ok(
|
||||
sub { pt_online_schema_change::main(@args,
|
||||
"$dsn,t=mysql.nonexistent_tbl", qw(--dry-run)) },
|
||||
"$dsn,D=mysql,t=nonexistent_tbl", qw(--dry-run)) },
|
||||
qr/`mysql`.`nonexistent_tbl` does not exist/,
|
||||
"Original table must exist"
|
||||
);
|
||||
@@ -69,7 +69,7 @@ $slave_dbh->do("USE pt_osc");
|
||||
$master_dbh->do("CREATE TRIGGER pt_osc.pt_osc_test AFTER DELETE ON pt_osc.t FOR EACH ROW DELETE FROM pt_osc.t WHERE 0");
|
||||
throws_ok(
|
||||
sub { pt_online_schema_change::main(@args,
|
||||
"$dsn,t=pt_osc.t", qw(--dry-run)) },
|
||||
"$dsn,D=pt_osc,t=t", qw(--dry-run)) },
|
||||
qr/`pt_osc`.`t` has triggers/,
|
||||
"Original table cannot have triggers"
|
||||
);
|
||||
@@ -80,7 +80,7 @@ $master_dbh->do("ALTER TABLE pt_osc.t DROP COLUMN id");
|
||||
$master_dbh->do("ALTER TABLE pt_osc.t DROP INDEX c");
|
||||
throws_ok(
|
||||
sub { pt_online_schema_change::main(@args,
|
||||
"$dsn,t=pt_osc.t", qw(--dry-run)) },
|
||||
"$dsn,D=pt_osc,t=t", qw(--dry-run)) },
|
||||
qr/`pt_osc`.`t` does not have a PRIMARY KEY or a unique index/,
|
||||
"Original table must have a PK or unique index"
|
||||
);
|
||||
@@ -101,7 +101,7 @@ for my $i ( 1..10 ) {
|
||||
|
||||
throws_ok(
|
||||
sub { pt_online_schema_change::main(@args,
|
||||
"$dsn,t=pt_osc.t", qw(--quiet --dry-run)) },
|
||||
"$dsn,D=pt_osc,t=t", qw(--quiet --dry-run)) },
|
||||
qr/Failed to find a unique new table name/,
|
||||
"Doesn't try forever to find a new table name"
|
||||
);
|
||||
|
Reference in New Issue
Block a user