mirror of
https://github.com/percona/percona-toolkit.git
synced 2025-09-11 13:40:07 +00:00
Use CleanupTask to ensure tool always, automatically cleans up. Move create triggers step before copy rows, to keep related code together.
This commit is contained in:
@@ -4825,6 +4825,55 @@ sub _d {
|
|||||||
# End Transformers package
|
# End Transformers package
|
||||||
# ###########################################################################
|
# ###########################################################################
|
||||||
|
|
||||||
|
# ###########################################################################
|
||||||
|
# CleanupTask package
|
||||||
|
# This package is a copy without comments from the original. The original
|
||||||
|
# with comments and its test file can be found in the Bazaar repository at,
|
||||||
|
# lib/CleanupTask.pm
|
||||||
|
# t/lib/CleanupTask.t
|
||||||
|
# See https://launchpad.net/percona-toolkit for more information.
|
||||||
|
# ###########################################################################
|
||||||
|
{
|
||||||
|
package CleanupTask;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings FATAL => 'all';
|
||||||
|
use English qw(-no_match_vars);
|
||||||
|
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
|
||||||
|
|
||||||
|
sub new {
|
||||||
|
my ( $class, $task ) = @_;
|
||||||
|
die "I need a task parameter" unless $task;
|
||||||
|
die "The task parameter must be a coderef" unless ref $task eq 'CODE';
|
||||||
|
my $self = {
|
||||||
|
task => $task,
|
||||||
|
};
|
||||||
|
PTDEBUG && _d('Created cleanup task', $task);
|
||||||
|
return bless $self, $class;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub DESTROY {
|
||||||
|
my ($self) = @_;
|
||||||
|
my $task = $self->{task};
|
||||||
|
PTDEBUG && _d('Calling cleanup task', $task);
|
||||||
|
$task->();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _d {
|
||||||
|
my ($package, undef, $line) = caller 0;
|
||||||
|
@_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
|
||||||
|
map { defined $_ ? $_ : 'undef' }
|
||||||
|
@_;
|
||||||
|
print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
|
}
|
||||||
|
# ###########################################################################
|
||||||
|
# End CleanupTask package
|
||||||
|
# ###########################################################################
|
||||||
|
|
||||||
# ###########################################################################
|
# ###########################################################################
|
||||||
# This is a combination of modules and programs in one -- a runnable module.
|
# This is a combination of modules and programs in one -- a runnable module.
|
||||||
# http://www.perl.com/pub/a/2006/07/13/lightning-articles.html?page=last
|
# http://www.perl.com/pub/a/2006/07/13/lightning-articles.html?page=last
|
||||||
@@ -4846,7 +4895,10 @@ $Data::Dumper::Indent = 1;
|
|||||||
$Data::Dumper::Sortkeys = 1;
|
$Data::Dumper::Sortkeys = 1;
|
||||||
$Data::Dumper::Quotekeys = 0;
|
$Data::Dumper::Quotekeys = 0;
|
||||||
|
|
||||||
my $oktorun = 1;
|
use sigtrap 'handler', \&sig_int, 'normal-signals';
|
||||||
|
|
||||||
|
my $exit_status = 0;
|
||||||
|
my $oktorun = 1;
|
||||||
my @drop_trigger_sqls;
|
my @drop_trigger_sqls;
|
||||||
|
|
||||||
$OUTPUT_AUTOFLUSH = 1;
|
$OUTPUT_AUTOFLUSH = 1;
|
||||||
@@ -4857,7 +4909,7 @@ sub main {
|
|||||||
$oktorun = 1;
|
$oktorun = 1;
|
||||||
@drop_trigger_sqls = ();
|
@drop_trigger_sqls = ();
|
||||||
|
|
||||||
my $exit_status = 0;
|
$exit_status = 0;
|
||||||
|
|
||||||
# ########################################################################
|
# ########################################################################
|
||||||
# Get configuration information.
|
# Get configuration information.
|
||||||
@@ -5114,13 +5166,12 @@ sub main {
|
|||||||
# may wait a very long time for slaves.
|
# may wait a very long time for slaves.
|
||||||
my $dbh = $cxn->dbh();
|
my $dbh = $cxn->dbh();
|
||||||
if ( !$dbh || !$dbh->ping() ) {
|
if ( !$dbh || !$dbh->ping() ) {
|
||||||
PTDEBUG && _d('Lost connection to master while waiting for slave lag');
|
|
||||||
eval { $dbh = $cxn->connect() }; # connect or die trying
|
eval { $dbh = $cxn->connect() }; # connect or die trying
|
||||||
if ( $EVAL_ERROR ) {
|
if ( $EVAL_ERROR ) {
|
||||||
$oktorun = 0; # Fatal error
|
$oktorun = 0; # flag for cleanup tasks
|
||||||
chomp $EVAL_ERROR;
|
chomp $EVAL_ERROR;
|
||||||
die "Lost connection to master while waiting for replica lag "
|
die "Lost connection to " . $cxn->name() . " while waiting for "
|
||||||
. "($EVAL_ERROR)";
|
. "replica lag ($EVAL_ERROR)\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$dbh->do("SELECT 'pt-online-schema-change keepalive'");
|
$dbh->do("SELECT 'pt-online-schema-change keepalive'");
|
||||||
@@ -5132,14 +5183,12 @@ sub main {
|
|||||||
my ($cxn) = @_;
|
my ($cxn) = @_;
|
||||||
my $dbh = $cxn->dbh();
|
my $dbh = $cxn->dbh();
|
||||||
if ( !$dbh || !$dbh->ping() ) {
|
if ( !$dbh || !$dbh->ping() ) {
|
||||||
PTDEBUG && _d('Lost connection to slave', $cxn->name(),
|
|
||||||
'while waiting for slave lag');
|
|
||||||
eval { $dbh = $cxn->connect() }; # connect or die trying
|
eval { $dbh = $cxn->connect() }; # connect or die trying
|
||||||
if ( $EVAL_ERROR ) {
|
if ( $EVAL_ERROR ) {
|
||||||
$oktorun = 0; # Fatal error
|
$oktorun = 0; # flag for cleanup tasks
|
||||||
chomp $EVAL_ERROR;
|
chomp $EVAL_ERROR;
|
||||||
die "Lost connection to replica " . $cxn->name()
|
die "Lost connection to replica " . $cxn->name()
|
||||||
. " while attempting to get its lag ($EVAL_ERROR)";
|
. " while attempting to get its lag ($EVAL_ERROR)\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $ms->get_slave_lag($dbh);
|
return $ms->get_slave_lag($dbh);
|
||||||
@@ -5279,6 +5328,51 @@ sub main {
|
|||||||
return $exit_status;
|
return $exit_status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ########################################################################
|
||||||
|
# Create a cleanup task object to undo changes (i.e. clean up) if the
|
||||||
|
# code dies, or we may call this explicitly at the end if all goes well.
|
||||||
|
# ########################################################################
|
||||||
|
my @cleanup_tasks;
|
||||||
|
my $cleanup = new CleanupTask(
|
||||||
|
sub {
|
||||||
|
my $original_error = $EVAL_ERROR;
|
||||||
|
foreach my $task ( reverse @cleanup_tasks ) {
|
||||||
|
eval {
|
||||||
|
$task->();
|
||||||
|
};
|
||||||
|
if ( $EVAL_ERROR ) {
|
||||||
|
warn "Error cleaning up: $EVAL_ERROR\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
die $original_error if $original_error; # rethrow original error
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
# The last cleanup task is to report whether or not the orig table
|
||||||
|
# was altered.
|
||||||
|
push @cleanup_tasks, sub {
|
||||||
|
PTDEBUG && _d('Clean up done, report if orig table was altered');
|
||||||
|
if ( $o->get('dry-run') ) {
|
||||||
|
print "Dry run complete. $orig_tbl->{name} was not altered.\n";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if ( $orig_tbl->{swapped} ) {
|
||||||
|
if ( $orig_tbl->{success} ) {
|
||||||
|
print "Successfully altered $orig_tbl->{name}.\n";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
print "Altered $orig_tbl->{name} but there were errors "
|
||||||
|
. "or warnings.\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
print "$orig_tbl->{name} was not altered.\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
# ########################################################################
|
# ########################################################################
|
||||||
# Check and create PID file if user specified --pid.
|
# Check and create PID file if user specified --pid.
|
||||||
# ########################################################################
|
# ########################################################################
|
||||||
@@ -5290,7 +5384,7 @@ sub main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# #####################################################################
|
# #####################################################################
|
||||||
# Create and alter the new table (but do not copy rows yet).
|
# Step 1: Create the new table.
|
||||||
# #####################################################################
|
# #####################################################################
|
||||||
my $new_tbl;
|
my $new_tbl;
|
||||||
eval {
|
eval {
|
||||||
@@ -5303,13 +5397,53 @@ sub main {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
if ( $EVAL_ERROR ) {
|
if ( $EVAL_ERROR ) {
|
||||||
die "Error creating new table: $EVAL_ERROR\n"
|
die "Error creating new table: $EVAL_ERROR\n";
|
||||||
. "$orig_tbl->{name} was not altered.\n";
|
|
||||||
}
|
}
|
||||||
my $drop_new_table_sql = "DROP TABLE IF EXISTS $new_tbl->{name};";
|
|
||||||
|
|
||||||
# Alter the new, empty table. This should be very quick, or die if
|
# If the new table still exists, drop it unless the tool was interrupted.
|
||||||
# the user specified a bad alter statement.
|
push @cleanup_tasks, sub {
|
||||||
|
PTDEBUG && _('Clean up new table');
|
||||||
|
my $new_tbl_exists = $tp->check_table(
|
||||||
|
dbh => $cxn->dbh(),
|
||||||
|
db => $new_tbl->{db},
|
||||||
|
tbl => $new_tbl->{tbl},
|
||||||
|
);
|
||||||
|
PTDEBUG && _d('New table exists:', $new_tbl_exists ? 'yes' : 'no');
|
||||||
|
return unless $new_tbl_exists;
|
||||||
|
|
||||||
|
my $sql = "DROP TABLE IF EXISTS $new_tbl->{name};";
|
||||||
|
if ( !$oktorun ) {
|
||||||
|
# The tool was interrupted, so do not drop the new table
|
||||||
|
# in case the user wants to resume (once resume capability
|
||||||
|
# is implemented).
|
||||||
|
print "Not dropping the new table $new_tbl->{name} because "
|
||||||
|
. "the tool was interrupted. To drop the new table, "
|
||||||
|
. "execute:\n$sql\n";
|
||||||
|
}
|
||||||
|
elsif ( $orig_tbl->{copied} && !$orig_tbl->{swapped} ) {
|
||||||
|
print "Not dropping the new table $new_tbl->{name} because "
|
||||||
|
. "--swap-tables failed. To drop the new table, "
|
||||||
|
. "execute:\n$sql\n";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
print "Dropping new table...\n";
|
||||||
|
print $sql, "\n" if $o->get('print');
|
||||||
|
PTDEBUG && _d($sql);
|
||||||
|
eval {
|
||||||
|
$cxn->dbh()->do($sql);
|
||||||
|
};
|
||||||
|
if ( $EVAL_ERROR ) {
|
||||||
|
warn "Error dropping new table $new_tbl->{name}: $EVAL_ERROR\n"
|
||||||
|
. "To try dropping the new table again, execute:\n$sql\n";
|
||||||
|
}
|
||||||
|
print "Dropped new table OK.\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
# #####################################################################
|
||||||
|
# Step 2: Alter the new, empty table. This should be very quick,
|
||||||
|
# or die if the user specified a bad alter statement.
|
||||||
|
# #####################################################################
|
||||||
if ( my $alter = $o->get('alter') ) {
|
if ( my $alter = $o->get('alter') ) {
|
||||||
print "Altering new table...\n";
|
print "Altering new table...\n";
|
||||||
my $sql = "ALTER TABLE $new_tbl->{name} $alter";
|
my $sql = "ALTER TABLE $new_tbl->{name} $alter";
|
||||||
@@ -5320,15 +5454,13 @@ sub main {
|
|||||||
};
|
};
|
||||||
if ( $EVAL_ERROR ) {
|
if ( $EVAL_ERROR ) {
|
||||||
die "Error altering new table $new_tbl->{name}: $EVAL_ERROR\n"
|
die "Error altering new table $new_tbl->{name}: $EVAL_ERROR\n"
|
||||||
. "Please verify the --alter statement and try again. "
|
|
||||||
. "To drop the new table, execute: $drop_new_table_sql\n\n"
|
|
||||||
. "$orig_tbl->{name} was not altered.\n";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
print "Altered $new_tbl->{name} OK.\n"
|
print "Altered $new_tbl->{name} OK.\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get the new table struct.
|
# Get the new table struct. This shouldn't die because
|
||||||
|
# we just created the table successfully so we know it's
|
||||||
|
# there. But the ghost of Ryan is everywhere.
|
||||||
my $ddl = $tp->get_create_table(
|
my $ddl = $tp->get_create_table(
|
||||||
$cxn->dbh(),
|
$cxn->dbh(),
|
||||||
$new_tbl->{db},
|
$new_tbl->{db},
|
||||||
@@ -5336,9 +5468,15 @@ sub main {
|
|||||||
);
|
);
|
||||||
$new_tbl->{tbl_struct} = $tp->parse($ddl);
|
$new_tbl->{tbl_struct} = $tp->parse($ddl);
|
||||||
|
|
||||||
# #####################################################################
|
# Determine what columns the original and new table share.
|
||||||
# Determine what columns the original and new tables have in common.
|
# If the user drops a col, that's easy: just don't copy it. If they
|
||||||
# #####################################################################
|
# add a column, it must have a default value. Other alterations
|
||||||
|
# may or may not affect the copy process--we'll know when we try!
|
||||||
|
# Note: we don't want to examine the --alter statement to see if the
|
||||||
|
# cols have changed because that's messy and prone to parsing errors.
|
||||||
|
# Col posn (position) is just for looks because user's like
|
||||||
|
# to see columns listed in their original order, not Perl's
|
||||||
|
# random hash key sorting.
|
||||||
my $col_posn = $orig_tbl->{tbl_struct}->{col_posn};
|
my $col_posn = $orig_tbl->{tbl_struct}->{col_posn};
|
||||||
my $orig_cols = $orig_tbl->{tbl_struct}->{is_col};
|
my $orig_cols = $orig_tbl->{tbl_struct}->{is_col};
|
||||||
my $new_cols = $new_tbl->{tbl_struct}->{is_col};
|
my $new_cols = $new_tbl->{tbl_struct}->{is_col};
|
||||||
@@ -5347,18 +5485,60 @@ sub main {
|
|||||||
keys %$orig_cols;
|
keys %$orig_cols;
|
||||||
PTDEBUG && _d('Common columns', @common_cols);
|
PTDEBUG && _d('Common columns', @common_cols);
|
||||||
|
|
||||||
|
# ########################################################################
|
||||||
|
# Step 3: Create the triggers to capture changes on the original table and
|
||||||
|
# apply them to the new table.
|
||||||
|
# ########################################################################
|
||||||
|
|
||||||
|
# Drop the triggers. We can save this cleanup task before
|
||||||
|
# adding the triggers because if adding them fails, this will be
|
||||||
|
# called which will drop whichever triggers were created.
|
||||||
|
push @cleanup_tasks, sub {
|
||||||
|
PTDEBUG && _d('Clean up triggers');
|
||||||
|
if ( $oktorun ) {
|
||||||
|
drop_triggers(
|
||||||
|
tbl => $orig_tbl,
|
||||||
|
Cxn => $cxn,
|
||||||
|
Quoter => $q,
|
||||||
|
OptionParser => $o,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
print "Not dropping triggers because the tool was interrupted. "
|
||||||
|
. "To drop the triggers, execute:\n"
|
||||||
|
. join("\n", @drop_trigger_sqls) . "\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
eval {
|
||||||
|
create_triggers(
|
||||||
|
orig_tbl => $orig_tbl,
|
||||||
|
new_tbl => $new_tbl,
|
||||||
|
columns => \@common_cols,
|
||||||
|
Cxn => $cxn,
|
||||||
|
Quoter => $q,
|
||||||
|
OptionParser => $o,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
if ( $EVAL_ERROR ) {
|
||||||
|
die "Error creating triggers: $EVAL_ERROR\n";
|
||||||
|
};
|
||||||
|
|
||||||
# #####################################################################
|
# #####################################################################
|
||||||
# Make a nibble iterator to copys rows from the orig to new table.
|
# Step 4: Copy rows.
|
||||||
# #####################################################################
|
# #####################################################################
|
||||||
|
|
||||||
|
# The hashref of callbacks below is what NibbleIterator calls internally
|
||||||
|
# to do all the copy work. The callbacks do not need to eval their work
|
||||||
|
# because the higher call to $nibble_iter->next() is eval'ed which will
|
||||||
|
# catch any errors in the callbacks.
|
||||||
my $total_rows = 0;
|
my $total_rows = 0;
|
||||||
my $total_time = 0;
|
my $total_time = 0;
|
||||||
my $avg_rate = 0; # rows/second
|
my $avg_rate = 0; # rows/second
|
||||||
my $limit = $o->get('chunk-size-limit');
|
my $retry = new Retry(); # for retrying to exec the copy statement
|
||||||
my $chunk_time = $o->get('chunk-time');
|
my $limit = $o->get('chunk-size-limit'); # brevity
|
||||||
my $retry = new Retry();
|
my $chunk_time = $o->get('chunk-time'); # brevity
|
||||||
|
|
||||||
# Callbacks for each table's nibble iterator. All checksum work is done
|
|
||||||
# in these callbacks and the subs that they call.
|
|
||||||
my $callbacks = {
|
my $callbacks = {
|
||||||
init => sub {
|
init => sub {
|
||||||
my (%args) = @_;
|
my (%args) = @_;
|
||||||
@@ -5382,7 +5562,6 @@ sub main {
|
|||||||
print $statements->{$sth}->{Statement}, "\n";
|
print $statements->{$sth}->{Statement}, "\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return unless $o->get('execute');
|
return unless $o->get('execute');
|
||||||
@@ -5443,7 +5622,6 @@ sub main {
|
|||||||
vals => [ @{$boundary->{lower}}, $nibble_iter->chunk_size() ],
|
vals => [ @{$boundary->{lower}}, $nibble_iter->chunk_size() ],
|
||||||
);
|
);
|
||||||
if (lc($expl->{key} || '') ne lc($nibble_iter->nibble_index() || '')) {
|
if (lc($expl->{key} || '') ne lc($nibble_iter->nibble_index() || '')) {
|
||||||
PTDEBUG && _d('Cannot nibble next chunk, aborting table');
|
|
||||||
my $msg
|
my $msg
|
||||||
= "Aborting copying table $tbl->{name} at chunk "
|
= "Aborting copying table $tbl->{name} at chunk "
|
||||||
. ($nibble_iter->nibble_number() + 1)
|
. ($nibble_iter->nibble_number() + 1)
|
||||||
@@ -5487,8 +5665,6 @@ sub main {
|
|||||||
# Ensure that MySQL is using the chunk index.
|
# Ensure that MySQL is using the chunk index.
|
||||||
if ( lc($expl->{key} || '')
|
if ( lc($expl->{key} || '')
|
||||||
ne lc($nibble_iter->nibble_index() || '') ) {
|
ne lc($nibble_iter->nibble_index() || '') ) {
|
||||||
PTDEBUG && _d('Chunk', $args{nibbleno}, 'of table',
|
|
||||||
"$tbl->{db}.$tbl->{tbl} not using chunk index, skipping");
|
|
||||||
my $msg
|
my $msg
|
||||||
= "Aborting copying table $tbl->{name} at chunk "
|
= "Aborting copying table $tbl->{name} at chunk "
|
||||||
. $nibble_iter->nibble_number()
|
. $nibble_iter->nibble_number()
|
||||||
@@ -5512,8 +5688,6 @@ sub main {
|
|||||||
&& $nibble_iter->identical_boundaries(
|
&& $nibble_iter->identical_boundaries(
|
||||||
$boundary->{upper}, $boundary->{next_lower}) )
|
$boundary->{upper}, $boundary->{next_lower}) )
|
||||||
{
|
{
|
||||||
PTDEBUG && _d('Chunk', $args{nibbleno}, 'of table',
|
|
||||||
"$tbl->{db}.$tbl->{tbl} is too large, skipping");
|
|
||||||
my $msg
|
my $msg
|
||||||
= "Aborting copying table $tbl->{name} at chunk "
|
= "Aborting copying table $tbl->{name} at chunk "
|
||||||
. $nibble_iter->nibble_number()
|
. $nibble_iter->nibble_number()
|
||||||
@@ -5599,7 +5773,8 @@ sub main {
|
|||||||
$replica_lag_pr->start() if $replica_lag_pr;
|
$replica_lag_pr->start() if $replica_lag_pr;
|
||||||
$replica_lag->wait(Progress => $replica_lag_pr);
|
$replica_lag->wait(Progress => $replica_lag_pr);
|
||||||
|
|
||||||
# Wait forever for system load to abate.
|
# Wait forever for system load to abate. wait() will die if
|
||||||
|
# --critical load is reached.
|
||||||
$sys_load_pr->start() if $sys_load_pr;
|
$sys_load_pr->start() if $sys_load_pr;
|
||||||
$sys_load->wait(Progress => $sys_load_pr);
|
$sys_load->wait(Progress => $sys_load_pr);
|
||||||
|
|
||||||
@@ -5616,7 +5791,7 @@ sub main {
|
|||||||
# "FROM $orig_table->{name} WHERE <nibble stuff>".
|
# "FROM $orig_table->{name} WHERE <nibble stuff>".
|
||||||
my $dml = "INSERT LOW_PRIORITY IGNORE INTO $new_tbl->{name} "
|
my $dml = "INSERT LOW_PRIORITY IGNORE INTO $new_tbl->{name} "
|
||||||
. "(" . join(', ', map { $q->quote($_) } @common_cols) . ") "
|
. "(" . join(', ', map { $q->quote($_) } @common_cols) . ") "
|
||||||
. "SELECT ";
|
. "SELECT";
|
||||||
my $select = join(', ', map { $q->quote($_) } @common_cols);
|
my $select = join(', ', map { $q->quote($_) } @common_cols);
|
||||||
|
|
||||||
# The chunk size is auto-adjusted, so use --chunk-size as
|
# The chunk size is auto-adjusted, so use --chunk-size as
|
||||||
@@ -5624,29 +5799,25 @@ sub main {
|
|||||||
# chunk size in the table data struct.
|
# chunk size in the table data struct.
|
||||||
$orig_tbl->{chunk_size} = $o->get('chunk-size');
|
$orig_tbl->{chunk_size} = $o->get('chunk-size');
|
||||||
|
|
||||||
my $nibble_iter;
|
# This won't (shouldn't) fail because we already verified in
|
||||||
eval {
|
# check_orig_table() table we can NibbleIterator::can_nibble().
|
||||||
$nibble_iter = new NibbleIterator(
|
my $nibble_iter = new NibbleIterator(
|
||||||
Cxn => $cxn,
|
Cxn => $cxn,
|
||||||
tbl => $orig_tbl,
|
tbl => $orig_tbl,
|
||||||
chunk_size => $orig_tbl->{chunk_size},
|
chunk_size => $orig_tbl->{chunk_size},
|
||||||
chunk_index => $o->get('chunk-index'),
|
chunk_index => $o->get('chunk-index'),
|
||||||
dml => $dml,
|
dml => $dml,
|
||||||
select => $select,
|
select => $select,
|
||||||
callbacks => $callbacks,
|
callbacks => $callbacks,
|
||||||
OptionParser => $o,
|
OptionParser => $o,
|
||||||
Quoter => $q,
|
Quoter => $q,
|
||||||
TableParser => $tp,
|
TableParser => $tp,
|
||||||
TableNibbler => new TableNibbler(TableParser => $tp, Quoter => $q),
|
TableNibbler => new TableNibbler(TableParser => $tp, Quoter => $q),
|
||||||
comments => {
|
comments => {
|
||||||
bite => "pt-online-schema-change $PID copy table",
|
bite => "pt-online-schema-change $PID copy table",
|
||||||
nibble => "pt-online-schema-change $PID copy nibble",
|
nibble => "pt-online-schema-change $PID copy nibble",
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
};
|
|
||||||
if ( $EVAL_ERROR ) {
|
|
||||||
die "Cannot chunk table $orig_tbl->{name}: $EVAL_ERROR\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
# Init a new weighted avg rate calculator for the table.
|
# Init a new weighted avg rate calculator for the table.
|
||||||
$orig_tbl->{rate} = new WeightedAvgRate(target_t => $chunk_time);
|
$orig_tbl->{rate} = new WeightedAvgRate(target_t => $chunk_time);
|
||||||
@@ -5665,68 +5836,21 @@ sub main {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
# ########################################################################
|
# Start copying rows. This may take awhile, but --progress is on
|
||||||
# Create the triggers to capture changes on the original table and
|
# by default so there will be progress updates to stderr.
|
||||||
# apply them to the new table.
|
|
||||||
# ########################################################################
|
|
||||||
eval {
|
|
||||||
create_triggers(
|
|
||||||
orig_tbl => $orig_tbl,
|
|
||||||
new_tbl => $new_tbl,
|
|
||||||
columns => \@common_cols,
|
|
||||||
Cxn => $cxn,
|
|
||||||
Quoter => $q,
|
|
||||||
OptionParser => $o,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
if ( $EVAL_ERROR ) {
|
|
||||||
warn "Error creating triggers: $EVAL_ERROR\n";
|
|
||||||
|
|
||||||
# Drop whatever triggers were recreated.
|
|
||||||
# This sub warns about its own errors.
|
|
||||||
drop_triggers(
|
|
||||||
tbl => $orig_tbl,
|
|
||||||
Cxn => $cxn,
|
|
||||||
Quoter => $q,
|
|
||||||
OptionParser => $o,
|
|
||||||
);
|
|
||||||
|
|
||||||
warn "The new table $new_tbl->{name} has not been dropped. "
|
|
||||||
. "To drop it, execute: $drop_new_table_sql\n\n"
|
|
||||||
. "$orig_tbl->{name} has not been modified.\n";
|
|
||||||
|
|
||||||
exit 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
# #####################################################################
|
|
||||||
# Copy rows from new table to old table.
|
|
||||||
# #####################################################################
|
|
||||||
eval {
|
eval {
|
||||||
1 while $nibble_iter->next();
|
1 while $nibble_iter->next();
|
||||||
};
|
};
|
||||||
if ( $EVAL_ERROR ) {
|
if ( $EVAL_ERROR ) {
|
||||||
warn "Error copying rows from $orig_tbl->{name} to "
|
die "Error copying rows from $orig_tbl->{name} to "
|
||||||
. "$new_tbl->{name}: $EVAL_ERROR\n";
|
. "$new_tbl->{name}: $EVAL_ERROR\n";
|
||||||
|
|
||||||
# Drop the triggers. This warns about its own errors.
|
|
||||||
drop_triggers(
|
|
||||||
tbl => $orig_tbl,
|
|
||||||
Cxn => $cxn,
|
|
||||||
Quoter => $q,
|
|
||||||
OptionParser => $o,
|
|
||||||
);
|
|
||||||
|
|
||||||
warn "The new table $new_tbl->{name} has not been dropped. "
|
|
||||||
. "To drop it, execute: $drop_new_table_sql\n\n"
|
|
||||||
. "$orig_tbl->{name} has not been modified.\n";
|
|
||||||
|
|
||||||
exit 1;
|
|
||||||
}
|
}
|
||||||
|
$orig_tbl->{copied} = 1; # flag for cleanup tasks
|
||||||
|
|
||||||
# #####################################################################
|
# #####################################################################
|
||||||
# XXX
|
# XXX
|
||||||
# Rename tables: orig -> old, new -> orig
|
# Step 5: Rename tables: orig -> old, new -> orig
|
||||||
# Past this point, the original table has been modified. This shouldn't
|
# Past this step, the original table has been altered. This shouldn't
|
||||||
# fail, but if it does, the failure could be serious depending on what
|
# fail, but if it does, the failure could be serious depending on what
|
||||||
# state the tables are left in.
|
# state the tables are left in.
|
||||||
# XXX
|
# XXX
|
||||||
@@ -5747,14 +5871,14 @@ sub main {
|
|||||||
die "Error swapping the tables: $EVAL_ERROR\n"
|
die "Error swapping the tables: $EVAL_ERROR\n"
|
||||||
. "Verify that the original table $orig_tbl->{name} has not "
|
. "Verify that the original table $orig_tbl->{name} has not "
|
||||||
. "been modified or renamed to the old table $old_tbl->{name}. "
|
. "been modified or renamed to the old table $old_tbl->{name}. "
|
||||||
. "Then drop the new table $new_tbl->{name} if it exists "
|
. "Then drop the new table $new_tbl->{name} if it exists.\n";
|
||||||
. "by executing: $drop_new_table_sql\n";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$orig_tbl->{swapped} = 1; # flag for cleanup tasks
|
||||||
PTDEBUG && _d('Old table:', Dumper($old_tbl));
|
PTDEBUG && _d('Old table:', Dumper($old_tbl));
|
||||||
|
|
||||||
# #####################################################################
|
# #####################################################################
|
||||||
# Update foreign key constraints if there are child tables.
|
# Step 6: Update foreign key constraints if there are child tables.
|
||||||
# #####################################################################
|
# #####################################################################
|
||||||
if ( $child_tables ) {
|
if ( $child_tables ) {
|
||||||
eval {
|
eval {
|
||||||
@@ -5805,23 +5929,7 @@ sub main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# ########################################################################
|
# ########################################################################
|
||||||
# Drop the triggers.
|
# Step 7: Drop the old table.
|
||||||
# ########################################################################
|
|
||||||
my $drop_triggers_errors = drop_triggers(
|
|
||||||
tbl => $orig_tbl,
|
|
||||||
Cxn => $cxn,
|
|
||||||
Quoter => $q,
|
|
||||||
OptionParser => $o,
|
|
||||||
);
|
|
||||||
if ( $drop_triggers_errors ) {
|
|
||||||
# The sub has already warned about its own errors.
|
|
||||||
warn "To try to drop the triggers again, execute:\n"
|
|
||||||
. join("\n", @drop_trigger_sqls) . "\n\n"
|
|
||||||
. "$orig_tbl->{name} has been modified.\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
# ########################################################################
|
|
||||||
# Drop the old table.
|
|
||||||
# ########################################################################
|
# ########################################################################
|
||||||
if ( $o->get('drop-old-table') ) {
|
if ( $o->get('drop-old-table') ) {
|
||||||
if ( $o->get('dry-run') ) {
|
if ( $o->get('dry-run') ) {
|
||||||
@@ -5836,8 +5944,7 @@ sub main {
|
|||||||
$cxn->dbh()->do($sql);
|
$cxn->dbh()->do($sql);
|
||||||
};
|
};
|
||||||
if ( $EVAL_ERROR ) {
|
if ( $EVAL_ERROR ) {
|
||||||
die "Error dropping the old table: $EVAL_ERROR\n"
|
die "Error dropping the old table: $EVAL_ERROR\n";
|
||||||
. "$orig_tbl->{name} has been modified.\n";
|
|
||||||
}
|
}
|
||||||
print "Dropped old table $old_tbl->{name} OK.\n";
|
print "Dropped old table $old_tbl->{name} OK.\n";
|
||||||
}
|
}
|
||||||
@@ -5846,25 +5953,8 @@ sub main {
|
|||||||
# ########################################################################
|
# ########################################################################
|
||||||
# Done.
|
# Done.
|
||||||
# ########################################################################
|
# ########################################################################
|
||||||
if ( $o->get('dry-run') ) {
|
$orig_tbl->{success} = 1; # flag for cleanup tasks
|
||||||
print "Dropping new table because this a dry run...\n";
|
$cleanup = undef; # exec cleanup tasks
|
||||||
print $drop_new_table_sql, "\n" if $o->get('print');
|
|
||||||
PTDEBUG && _d($drop_new_table_sql);
|
|
||||||
eval {
|
|
||||||
$cxn->dbh()->do($drop_new_table_sql);
|
|
||||||
};
|
|
||||||
if ( $EVAL_ERROR ) {
|
|
||||||
print "Error drop new table: $EVAL_ERROR\n";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
print "Dropped new table $new_tbl->{name} OK.\n";
|
|
||||||
}
|
|
||||||
print "Dry run complete. $orig_tbl->{name} was not altered.\n";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
# BARON: I think it's good to end with a clear indication of success.
|
|
||||||
print "Successfully altered $orig_tbl->{name}.\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
return $exit_status;
|
return $exit_status;
|
||||||
}
|
}
|
||||||
@@ -6032,6 +6122,7 @@ sub check_orig_table {
|
|||||||
Cxn => $cxn,
|
Cxn => $cxn,
|
||||||
tbl => $orig_tbl,
|
tbl => $orig_tbl,
|
||||||
chunk_size => $o->get('chunk-size'),
|
chunk_size => $o->get('chunk-size'),
|
||||||
|
chunk_indx => $o->get('chunk-index'),
|
||||||
OptionParser => $o,
|
OptionParser => $o,
|
||||||
TableParser => $tp,
|
TableParser => $tp,
|
||||||
);
|
);
|
||||||
@@ -6319,7 +6410,8 @@ sub create_triggers {
|
|||||||
# (or faked to be created) so if the 2nd trigger
|
# (or faked to be created) so if the 2nd trigger
|
||||||
# fails to create, we know to only drop the 1st.
|
# fails to create, we know to only drop the 1st.
|
||||||
push @drop_trigger_sqls,
|
push @drop_trigger_sqls,
|
||||||
"DROP TRIGGER IF EXISTS " . $q->quote($orig_tbl->{db}, "pt_osc_$name");
|
"DROP TRIGGER IF EXISTS "
|
||||||
|
. $q->quote($orig_tbl->{db}, "pt_osc_$name") . ";";
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( $o->get('execute') ) {
|
if ( $o->get('execute') ) {
|
||||||
@@ -6337,8 +6429,6 @@ sub drop_triggers {
|
|||||||
}
|
}
|
||||||
my ($tbl, $cxn, $q, $o) = @args{@required_args};
|
my ($tbl, $cxn, $q, $o) = @args{@required_args};
|
||||||
|
|
||||||
my $exit_status = 0;
|
|
||||||
|
|
||||||
# This sub works for --dry-run and --execute, although --dry-run is
|
# This sub works for --dry-run and --execute, although --dry-run is
|
||||||
# only interesting with --print so the user can see the drop trigger
|
# only interesting with --print so the user can see the drop trigger
|
||||||
# statements for --execute.
|
# statements for --execute.
|
||||||
@@ -6349,9 +6439,8 @@ sub drop_triggers {
|
|||||||
print "Dropping triggers...\n";
|
print "Dropping triggers...\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
# The statements are shifted/removed so that if one fails, the caller
|
my @not_dropped;
|
||||||
# can report which triggers are left to drop manually.
|
foreach my $sql ( @drop_trigger_sqls ) {
|
||||||
while ( my $sql = shift @drop_trigger_sqls ) {
|
|
||||||
print $sql, "\n" if $o->get('print');
|
print $sql, "\n" if $o->get('print');
|
||||||
if ( $o->get('execute') ) {
|
if ( $o->get('execute') ) {
|
||||||
PTDEBUG && _d($sql);
|
PTDEBUG && _d($sql);
|
||||||
@@ -6360,16 +6449,23 @@ sub drop_triggers {
|
|||||||
};
|
};
|
||||||
if ( $EVAL_ERROR ) {
|
if ( $EVAL_ERROR ) {
|
||||||
warn "Error dropping trigger: $EVAL_ERROR\n";
|
warn "Error dropping trigger: $EVAL_ERROR\n";
|
||||||
|
push @not_dropped, $sql;
|
||||||
$exit_status = 1;
|
$exit_status = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( $o->get('execute') && $exit_status == 0) {
|
if ( $o->get('execute') ) {
|
||||||
print "Dropped triggers OK.\n";
|
if ( !@not_dropped ) {
|
||||||
|
print "Dropped triggers OK.\n";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
warn "To try dropping the triggers again, execute:\n"
|
||||||
|
. join("\n", @not_dropped) . "\n";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $exit_status;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub exec_nibble {
|
sub exec_nibble {
|
||||||
@@ -6544,6 +6640,14 @@ sub explain_statement {
|
|||||||
return $expl;
|
return $expl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Catches signals so we can exit gracefully.
|
||||||
|
sub sig_int {
|
||||||
|
my ( $signal ) = @_;
|
||||||
|
$oktorun = 0; # flag for cleanup tasks
|
||||||
|
print STDERR "# Exiting on SIG$signal.\n";
|
||||||
|
exit 1;
|
||||||
|
}
|
||||||
|
|
||||||
sub _d {
|
sub _d {
|
||||||
my ($package, undef, $line) = caller 0;
|
my ($package, undef, $line) = caller 0;
|
||||||
@_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
|
@_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
|
||||||
@@ -7112,7 +7216,7 @@ Socket file to use for connection.
|
|||||||
|
|
||||||
default: yes
|
default: yes
|
||||||
|
|
||||||
Swap the the original table and the new, altered table. This step
|
Swap the original table and the new, altered table. This step
|
||||||
essentially completes the online schema change process by making the
|
essentially completes the online schema change process by making the
|
||||||
temporary table with the new schema take the place of the original table.
|
temporary table with the new schema take the place of the original table.
|
||||||
The original tables becomes the "old table" and is dropped if
|
The original tables becomes the "old table" and is dropped if
|
||||||
|
@@ -9,7 +9,7 @@ BEGIN {
|
|||||||
use strict;
|
use strict;
|
||||||
use warnings FATAL => 'all';
|
use warnings FATAL => 'all';
|
||||||
use English qw(-no_match_vars);
|
use English qw(-no_match_vars);
|
||||||
use Test::More tests => 2;
|
use Test::More tests => 4;
|
||||||
|
|
||||||
use PerconaTest;
|
use PerconaTest;
|
||||||
use CleanupTask;
|
use CleanupTask;
|
||||||
@@ -30,6 +30,23 @@ is(
|
|||||||
"Cleanup task called after obj destroyed"
|
"Cleanup task called after obj destroyed"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
$foo = 0;
|
||||||
|
my $set_foo = new CleanupTask(sub { $foo = 42; });
|
||||||
|
|
||||||
|
is(
|
||||||
|
$foo,
|
||||||
|
0,
|
||||||
|
"Cleanup task not called yet"
|
||||||
|
);
|
||||||
|
|
||||||
|
$set_foo = undef;
|
||||||
|
is(
|
||||||
|
$foo,
|
||||||
|
42,
|
||||||
|
"Cleanup task called after obj=undef"
|
||||||
|
);
|
||||||
|
|
||||||
# #############################################################################
|
# #############################################################################
|
||||||
# Done.
|
# Done.
|
||||||
# #############################################################################
|
# #############################################################################
|
||||||
|
Reference in New Issue
Block a user