Compare commits

...

15 Commits

Author SHA1 Message Date
Carlos Salguero
13f7afc49d PT-1757 Fixed NibbleIterator undef 2019-10-14 14:44:57 -03:00
Carlos Salguero
6a302e2b07 PT-1757 Implemented fallback in NibbleIterator
Since now NibbleIterator can fallback to nibble (from one chunk) pt-osc
can nibble tables even if due to unacurate stats, it choses one nible as
the initial nibbling method.
2019-10-13 21:58:56 -03:00
Carlos Salguero
e58b9fbea8 PT-1757 fallback to nibble in NibbleIterator 2019-10-08 21:24:59 -03:00
Carlos Salguero
a6e0b0d324 WIP 2019-10-08 13:10:26 -03:00
Carlos Salguero
c107e4fb42 Merge pull request #418 from percona/PT-297
PT-297 Improve emuns handling in pt-osc
2019-09-20 13:51:31 -03:00
Carlos Salguero
9ed892692a PT-297 removed debug code 2019-09-20 13:50:44 -03:00
Carlos Salguero
10fe056242 WIP 2019-09-19 14:29:55 -03:00
Carlos Salguero
b03767a260 PT-297 WIP 2019-09-19 10:58:33 -03:00
Carlos Salguero
139dfd1dd4 Removed old key 2019-09-18 09:21:39 -03:00
Carlos Salguero
129e28fe4a Merge pull request #417 from percona/PT-1759
PT-1759 pt-stalk not collecting processlist and vars
2019-09-13 11:46:36 -03:00
Carlos Salguero
69105900f8 PT=1759 Updated changelog 2019-09-13 11:19:36 -03:00
Carlos Salguero
2499913b80 Merge branch '3.0' into PT-1759 2019-09-13 11:18:03 -03:00
Carlos Salguero
8174dfc22d PT-1759 pt-stalk not collecting processlist 2019-09-13 11:17:37 -03:00
Carlos Salguero
d37f016174 Merge pull request #413 from percona/release-3.1
Release 3.1
2019-09-13 11:17:01 -03:00
Carlos Salguero
210433269c Removed some versions from Gopkg.toml
- Removed mongodb driver version
- Removed logrus version
This is to let PMM use the toolkit without having deps version issues.
2019-09-13 08:46:01 -03:00
21 changed files with 698 additions and 503 deletions

View File

@@ -1,5 +1,9 @@
Changelog for Percona Toolkit
* Fixed bug PT-1759: pt-stalk not collecting processlist and variables
v3.1.0 release 2019-09-12
* Fixed bug PT-1114: pt-table-checksum fails when table is empty
* Fixed bug PT-1344: pt-online-schema-change: Use of uninitialized value $host in string
* Fixed bug PT-1575: pt-mysql-summary does not print PXC section for PXC 5.6 and 5.7

View File

@@ -58,8 +58,6 @@
[[constraint]]
name = "github.com/sirupsen/logrus"
version = "1.0.4"
[[constraint]]
name = "go.mongodb.org/mongo-driver"
version = "~1.0.0"

View File

@@ -3136,6 +3136,7 @@ sub generate_asc_stmt {
cols => \@cols,
quoter => $q,
is_nullable => $tbl_struct->{is_nullable},
type_for => $tbl_struct->{type_for},
);
$asc_stmt->{boundaries}->{$cmp} = $cmp_where->{where};
}
@@ -3156,6 +3157,7 @@ sub generate_cmp_where {
my @slice = @{$args{slice}};
my @cols = @{$args{cols}};
my $is_nullable = $args{is_nullable};
my $type_for = $args{type_for};
my $type = $args{type};
my $q = $self->{Quoter};
@@ -3172,13 +3174,14 @@ sub generate_cmp_where {
my $ord = $slice[$j];
my $col = $cols[$ord];
my $quo = $q->quote($col);
my $val = ($col && ($type_for->{$col} || '')) eq 'enum' ? "CAST(? AS UNSIGNED)" : "?";
if ( $is_nullable->{$col} ) {
push @clause, "((? IS NULL AND $quo IS NULL) OR ($quo = ?))";
push @clause, "(($val IS NULL AND $quo IS NULL) OR ($quo = $val))";
push @r_slice, $ord, $ord;
push @r_scols, $col, $col;
}
else {
push @clause, "$quo = ?";
push @clause, "$quo = $val";
push @r_slice, $ord;
push @r_scols, $col;
}
@@ -3188,15 +3191,16 @@ sub generate_cmp_where {
my $col = $cols[$ord];
my $quo = $q->quote($col);
my $end = $i == $#slice; # Last clause of the whole group.
my $val = ($col && ($type_for->{$col} || '')) eq 'enum' ? "CAST(? AS UNSIGNED)" : "?";
if ( $is_nullable->{$col} ) {
if ( $type =~ m/=/ && $end ) {
push @clause, "(? IS NULL OR $quo $type ?)";
push @clause, "($val IS NULL OR $quo $type $val)";
}
elsif ( $type =~ m/>/ ) {
push @clause, "((? IS NULL AND $quo IS NOT NULL) OR ($quo $cmp ?))";
push @clause, "(($val IS NULL AND $quo IS NOT NULL) OR ($quo $cmp $val)";
}
else { # If $type =~ m/</ ) {
push @clause, "((? IS NOT NULL AND $quo IS NULL) OR ($quo $cmp ?))";
push @clauses, "(($val IS NOT NULL AND $quo IS NULL) OR ($quo $cmp $val))";
}
push @r_slice, $ord, $ord;
push @r_scols, $col, $col;
@@ -3204,7 +3208,7 @@ sub generate_cmp_where {
else {
push @r_slice, $ord;
push @r_scols, $col;
push @clause, ($type =~ m/=/ && $end ? "$quo $type ?" : "$quo $cmp ?");
push @clause, ($type =~ m/=/ && $end ? "$quo $type $val" : "$quo $cmp $val");
}
push @clauses, '(' . join(' AND ', @clause) . ')';

View File

@@ -3019,6 +3019,7 @@ sub generate_asc_stmt {
cols => \@cols,
quoter => $q,
is_nullable => $tbl_struct->{is_nullable},
type_for => $tbl_struct->{type_for},
);
$asc_stmt->{boundaries}->{$cmp} = $cmp_where->{where};
}
@@ -3039,6 +3040,7 @@ sub generate_cmp_where {
my @slice = @{$args{slice}};
my @cols = @{$args{cols}};
my $is_nullable = $args{is_nullable};
my $type_for = $args{type_for};
my $type = $args{type};
my $q = $self->{Quoter};
@@ -3055,13 +3057,14 @@ sub generate_cmp_where {
my $ord = $slice[$j];
my $col = $cols[$ord];
my $quo = $q->quote($col);
my $val = ($col && ($type_for->{$col} || '')) eq 'enum' ? "CAST(? AS UNSIGNED)" : "?";
if ( $is_nullable->{$col} ) {
push @clause, "((? IS NULL AND $quo IS NULL) OR ($quo = ?))";
push @clause, "(($val IS NULL AND $quo IS NULL) OR ($quo = $val))";
push @r_slice, $ord, $ord;
push @r_scols, $col, $col;
}
else {
push @clause, "$quo = ?";
push @clause, "$quo = $val";
push @r_slice, $ord;
push @r_scols, $col;
}
@@ -3071,15 +3074,16 @@ sub generate_cmp_where {
my $col = $cols[$ord];
my $quo = $q->quote($col);
my $end = $i == $#slice; # Last clause of the whole group.
my $val = ($col && ($type_for->{$col} || '')) eq 'enum' ? "CAST(? AS UNSIGNED)" : "?";
if ( $is_nullable->{$col} ) {
if ( $type =~ m/=/ && $end ) {
push @clause, "(? IS NULL OR $quo $type ?)";
push @clause, "($val IS NULL OR $quo $type $val)";
}
elsif ( $type =~ m/>/ ) {
push @clause, "((? IS NULL AND $quo IS NOT NULL) OR ($quo $cmp ?))";
push @clause, "(($val IS NULL AND $quo IS NOT NULL) OR ($quo $cmp $val)";
}
else { # If $type =~ m/</ ) {
push @clause, "((? IS NOT NULL AND $quo IS NULL) OR ($quo $cmp ?))";
push @clauses, "(($val IS NOT NULL AND $quo IS NULL) OR ($quo $cmp $val))";
}
push @r_slice, $ord, $ord;
push @r_scols, $col, $col;
@@ -3087,7 +3091,7 @@ sub generate_cmp_where {
else {
push @r_slice, $ord;
push @r_scols, $col;
push @clause, ($type =~ m/=/ && $end ? "$quo $type ?" : "$quo $cmp ?");
push @clause, ($type =~ m/=/ && $end ? "$quo $type $val" : "$quo $cmp $val");
}
push @clauses, '(' . join(' AND ', @clause) . ')';
@@ -5484,82 +5488,131 @@ sub new {
my @cols = grep { !$ignore_col->{$_} } @$all_cols;
my $self;
if ( $nibble_params->{one_nibble} ) {
my $params = _one_nibble(\%args, \@cols, $where, $tbl, \%comments);
$self = {
%args,
one_nibble => 1,
limit => 0,
nibble_sql => $params->{nibble_sql},
explain_nibble_sql => $params->{explain_nibble_sql},
};
} else {
my $params = _nibble_params($nibble_params, $tbl, \%args, \@cols, $chunk_size, $where, \%comments, $q);
$self = {
%args,
index => $params->{index},
limit => $params->{limit},
first_lb_sql => $params->{first_lb_sql},
last_ub_sql => $params->{last_ub_sql},
ub_sql => $params->{ub_sql},
nibble_sql => $params->{nibble_sql},
explain_first_lb_sql => $params->{explain_first_lb_sql},
explain_ub_sql => $params->{explain_ub_sql},
explain_nibble_sql => $params->{explain_nibble_sql},
resume_lb_sql => $params->{resume_lb_sql},
sql => $params->{sql},
};
}
$self->{row_est} = $nibble_params->{row_est},
$self->{nibbleno} = 0;
$self->{have_rows} = 0;
$self->{rowno} = 0;
$self->{oktonibble} = 1;
$self->{pause_file} = $nibble_params->{pause_file};
$self->{sleep} = $args{sleep} || 60;
$self->{nibble_params} = $nibble_params;
$self->{tbl} = $tbl;
$self->{args} = \%args;
$self->{cols} = \@cols;
$self->{chunk_size} = $chunk_size;
$self->{where} = $where;
$self->{comments} = \%comments;
return bless $self, $class;
}
sub switch_to_nibble {
my $self = shift;
my $params = _nibble_params($self->{nibble_params}, $self->{tbl}, $self->{args}, $self->{cols},
$self->{chunk_size}, $self->{where}, $self->{comments}, $self->{Quoter});
$self->{one_nibble} = 0;
$self->{index} = $params->{index};
$self->{limit} = $params->{limit};
$self->{first_lb_sql} = $params->{first_lb_sql};
$self->{last_ub_sql} = $params->{last_ub_sql};
$self->{ub_sql} = $params->{ub_sql};
$self->{nibble_sql} = $params->{nibble_sql};
$self->{explain_first_lb_sql} = $params->{explain_first_lb_sql};
$self->{explain_ub_sql} = $params->{explain_ub_sql};
$self->{explain_nibble_sql} = $params->{explain_nibble_sql};
$self->{resume_lb_sql} = $params->{resume_lb_sql};
$self->{sql} = $params->{sql};
$self->_get_bounds();
$self->_prepare_sths();
}
sub _one_nibble {
my ($args, $cols, $where, $tbl, $comments) = @_;
my $q = new Quoter();
my $nibble_sql
= ($args{dml} ? "$args{dml} " : "SELECT ")
. ($args{select} ? $args{select}
: join(', ', map { $q->quote($_) } @cols))
= ($args->{dml} ? "$args->{dml} " : "SELECT ")
. ($args->{select} ? $args->{select}
: join(', ', map{ $tbl->{tbl_struct}->{type_for}->{$_} eq 'enum' ?
"CAST(".$q->quote($_)." AS UNSIGNED)" : $q->quote($_) } @$cols))
. " FROM $tbl->{name}"
. ($where ? " WHERE $where" : '')
. ($args{lock_in_share_mode} ? " LOCK IN SHARE MODE" : "")
. " /*$comments{bite}*/";
. ($args->{lock_in_share_mode} ? " LOCK IN SHARE MODE" : "")
. " /*$comments->{bite}*/";
PTDEBUG && _d('One nibble statement:', $nibble_sql);
my $explain_nibble_sql
= "EXPLAIN SELECT "
. ($args{select} ? $args{select}
: join(', ', map { $q->quote($_) } @cols))
. ($args->{select} ? $args->{select}
: join(', ', map{ $tbl->{tbl_struct}->{type_for}->{$_} eq 'enum'
? "CAST(".$q->quote($_)." AS UNSIGNED)" : $q->quote($_) } @$cols))
. " FROM $tbl->{name}"
. ($where ? " WHERE $where" : '')
. ($args{lock_in_share_mode} ? " LOCK IN SHARE MODE" : "")
. " /*explain $comments{bite}*/";
. ($args->{lock_in_share_mode} ? " LOCK IN SHARE MODE" : "")
. " /*explain $comments->{bite}*/";
PTDEBUG && _d('Explain one nibble statement:', $explain_nibble_sql);
$self = {
%args,
return {
one_nibble => 1,
limit => 0,
nibble_sql => $nibble_sql,
explain_nibble_sql => $explain_nibble_sql,
};
}
else {
}
sub _nibble_params {
my ($nibble_params, $tbl, $args, $cols, $chunk_size, $where, $comments, $q) = @_;
my $index = $nibble_params->{index}; # brevity
my $index_cols = $tbl->{tbl_struct}->{keys}->{$index}->{cols};
my $asc = $args{TableNibbler}->generate_asc_stmt(
%args,
my $asc = $args->{TableNibbler}->generate_asc_stmt(
%$args,
tbl_struct => $tbl->{tbl_struct},
index => $index,
n_index_cols => $args{n_chunk_index_cols},
cols => \@cols,
n_index_cols => $args->{n_chunk_index_cols},
cols => $cols,
asc_only => 1,
);
PTDEBUG && _d('Ascend params:', Dumper($asc));
my $force_concat_enums = $o->has('force-concat-enums') && $o->get('force-concat-enums');
my $i=0;
for my $index (@{$index_cols}) {
last if $args{n_chunk_index_cols} && $i >= $args{n_chunk_index_cols};
$i++;
if ($tbl->{tbl_struct}->{type_for}->{$index} eq 'enum') {
if ($tbl->{tbl_struct}->{defs}->{$index} =~ m/enum\s*\((.*?)\)/) {
my @items = split(/,\s*/, $1);
my $sorted = 1; # Asume the items list is sorted to later check if this is true
for (my $i=1; $i < scalar(@items); $i++) {
if ($items[$i-1] gt $items[$i]) {
$sorted = 0;
last;
}
}
if (!$force_concat_enums && !$sorted) {
die "The index " . $index . " in table " . $tbl->{name} .
" has unsorted enum items.\nPlease read the documentation for the --force-concat-enums parameter\n";
}
}
}
}
my $force_concat_enums;
my $from = "$tbl->{name} FORCE INDEX(`$index`)";
my $order_by = join(', ', map { $tbl->{tbl_struct}->{type_for}->{$_} eq 'enum' && $force_concat_enums
? "CONCAT(".$q->quote($_).")" : $q->quote($_)} @{$index_cols});
my $order_by_dec = join(' DESC,', map { $tbl->{tbl_struct}->{type_for}->{$_} eq 'enum' && $force_concat_enums
? "CONCAT(".$q->quote($_).")" : $q->quote($_)} @{$index_cols});
my $order_by = join(', ', map {$q->quote($_)} @{$index_cols});
my $order_by_dec = join(' DESC,', map {$q->quote($_)} @{$index_cols});
my $first_lb_sql
= "SELECT /*!40001 SQL_NO_CACHE */ "
. join(', ', map { $q->quote($_) } @{$asc->{scols}})
. join(', ', map { $tbl->{tbl_struct}->{type_for}->{$_} eq 'enum' ? "CAST(".$q->quote($_)." AS UNSIGNED)" : $q->quote($_)} @{$asc->{scols}})
. " FROM $from"
. ($where ? " WHERE $where" : '')
. " ORDER BY $order_by"
@@ -5568,10 +5621,10 @@ sub new {
PTDEBUG && _d('First lower boundary statement:', $first_lb_sql);
my $resume_lb_sql;
if ( $args{resume} ) {
if ( $args->{resume} ) {
$resume_lb_sql
= "SELECT /*!40001 SQL_NO_CACHE */ "
. join(', ', map { $q->quote($_) } @{$asc->{scols}})
. join(', ', map { $tbl->{tbl_struct}->{type_for}->{$_} eq 'enum' ? "CAST(".$q->quote($_)." AS UNSIGNED)" : $q->quote($_)} @{$asc->{scols}})
. " FROM $from"
. " WHERE " . $asc->{boundaries}->{'>'}
. ($where ? " AND ($where)" : '')
@@ -5583,7 +5636,7 @@ sub new {
my $last_ub_sql
= "SELECT /*!40001 SQL_NO_CACHE */ "
. join(', ', map { $q->quote($_) } @{$asc->{scols}})
. join(', ', map { $tbl->{tbl_struct}->{type_for}->{$_} eq 'enum' ? "CAST(".$q->quote($_)." AS UNSIGNED)" : $q->quote($_)} @{$asc->{scols}})
. " FROM $from"
. ($where ? " WHERE $where" : '')
. " ORDER BY "
@@ -5594,7 +5647,7 @@ sub new {
my $ub_sql
= "SELECT /*!40001 SQL_NO_CACHE */ "
. join(', ', map { $q->quote($_) } @{$asc->{scols}})
. join(', ', map { $tbl->{tbl_struct}->{type_for}->{$_} eq 'enum' ? "CAST(".$q->quote($_)." AS UNSIGNED)" : $q->quote($_)} @{$asc->{scols}})
. " FROM $from"
. " WHERE " . $asc->{boundaries}->{'>='}
. ($where ? " AND ($where)" : '')
@@ -5604,36 +5657,36 @@ sub new {
PTDEBUG && _d('Upper boundary statement:', $ub_sql);
my $nibble_sql
= ($args{dml} ? "$args{dml} " : "SELECT ")
. ($args{select} ? $args{select}
: join(', ', map { $q->quote($_) } @{$asc->{cols}}))
= ($args->{dml} ? "$args->{dml} " : "SELECT ")
. ($args->{select} ? $args->{select}
: join(', ', map { $tbl->{tbl_struct}->{type_for}->{$_} eq 'enum' ? "CAST(".$q->quote($_)." AS UNSIGNED)" : $q->quote($_)} @{$asc->{cols}}))
. " FROM $from"
. " WHERE " . $asc->{boundaries}->{'>='} # lower boundary
. " AND " . $asc->{boundaries}->{'<='} # upper boundary
. ($where ? " AND ($where)" : '')
. ($args{order_by} ? " ORDER BY $order_by" : "")
. ($args{lock_in_share_mode} ? " LOCK IN SHARE MODE" : "")
. " /*$comments{nibble}*/";
. ($args->{order_by} ? " ORDER BY $order_by" : "")
. ($args->{lock_in_share_mode} ? " LOCK IN SHARE MODE" : "")
. " /*$comments->{nibble}*/";
PTDEBUG && _d('Nibble statement:', $nibble_sql);
my $explain_nibble_sql
= "EXPLAIN SELECT "
. ($args{select} ? $args{select}
. ($args->{select} ? $args->{select}
: join(', ', map { $q->quote($_) } @{$asc->{cols}}))
. " FROM $from"
. " WHERE " . $asc->{boundaries}->{'>='} # lower boundary
. " AND " . $asc->{boundaries}->{'<='} # upper boundary
. ($where ? " AND ($where)" : '')
. ($args{order_by} ? " ORDER BY $order_by" : "")
. ($args{lock_in_share_mode} ? " LOCK IN SHARE MODE" : "")
. " /*explain $comments{nibble}*/";
. ($args->{order_by} ? " ORDER BY $order_by" : "")
. ($args->{lock_in_share_mode} ? " LOCK IN SHARE MODE" : "")
. " /*explain $comments->{nibble}*/";
PTDEBUG && _d('Explain nibble statement:', $explain_nibble_sql);
my $limit = $chunk_size - 1;
PTDEBUG && _d('Initial chunk size (LIMIT):', $limit);
$self = {
%args,
my $params = {
one_nibble => 0,
index => $index,
limit => $limit,
first_lb_sql => $first_lb_sql,
@@ -5652,17 +5705,7 @@ sub new {
order_by => $order_by,
},
};
}
$self->{row_est} = $nibble_params->{row_est},
$self->{nibbleno} = 0;
$self->{have_rows} = 0;
$self->{rowno} = 0;
$self->{oktonibble} = 1;
$self->{pause_file} = $nibble_params->{pause_file};
$self->{sleep} = $args{sleep} || 60;
return bless $self, $class;
return $params;
}
sub next {
@@ -5699,7 +5742,6 @@ sub next {
NIBBLE:
while ( $self->{have_rows} || $self->_next_boundaries() ) {
if ($self->{pause_file}) {
while(-f $self->{pause_file}) {
print "Sleeping $self->{sleep} seconds because $self->{pause_file} exists\n";
@@ -5720,7 +5762,7 @@ sub next {
if ( !$self->{have_rows} ) {
$self->{nibbleno}++;
PTDEBUG && _d('Nibble:', $self->{nibble_sth}->{Statement}, 'params:',
join(', ', (@{$self->{lower}} || [], @{$self->{upper} }||[])));
join(', ', (@{$self->{lower} || []}, @{$self->{upper} || []})));
if ( my $callback = $self->{callbacks}->{exec_nibble} ) {
$self->{have_rows} = $callback->(%callback_args);
}
@@ -6119,7 +6161,7 @@ sub _next_boundaries {
PTDEBUG && _d($self->{ub_sth}->{Statement}, 'params:',
join(', ', @{$self->{lower}} || []), $self->{limit});
join(', ', @{$self->{lower}}), $self->{limit});
$self->{ub_sth}->execute(@{$self->{lower}}, $self->{limit});
my $boundary = $self->{ub_sth}->fetchall_arrayref();
PTDEBUG && _d('Next boundary:', Dumper($boundary));
@@ -6160,10 +6202,7 @@ sub identical_boundaries {
if scalar @$b1 != scalar @$b2; # shouldn't happen
my $n_vals = scalar @$b1;
for my $i ( 0..($n_vals-1) ) {
next if (!defined($b1->[$i]) && !defined($b2->[$i]));
return 0 if (!defined($b1->[$i]) && defined($b2->[$i])); # diff
return 0 if (defined($b1->[$i]) && !defined($b2->[$i])); # diff
return 0 if $b1->[$i] ne $b2->[$i]; # diff
return 0 if ($b1->[$i] || '') ne ($b2->[$i] || ''); # diff
}
return 1;
}
@@ -6626,6 +6665,7 @@ use English qw(-no_match_vars);
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
use Data::Dumper;
use Carp;
$Data::Dumper::Indent = 1;
$Data::Dumper::Sortkeys = 1;
$Data::Dumper::Quotekeys = 0;
@@ -6638,7 +6678,7 @@ sub new {
}
my $self = {
Quoter => $args{Quoter},
Quoter => $args{Quoter},
};
return bless $self, $class;
@@ -6695,8 +6735,16 @@ sub _get_first_values {
my $index_struct = $tbl->{tbl_struct}->{keys}->{$index};
my $index_cols = $index_struct->{cols};
my $index_columns = join (', ',
my $index_columns;
eval {
$index_columns = join (', ',
map { $q->quote($_) } @{$index_cols}[0..($n_index_cols - 1)]);
};
if ($EVAL_ERROR) {
confess "$EVAL_ERROR";
}
my @where;
foreach my $col ( @{$index_cols}[0..($n_index_cols - 1)] ) {
@@ -6730,14 +6778,15 @@ sub _make_range_query {
if ( $n_index_cols > 1 ) {
foreach my $n ( 0..($n_index_cols - 2) ) {
my $col = $index_cols->[$n];
my $val = $vals->[$n];
push @where, $q->quote($col) . " = ?";
my $val = $tbl->{tbl_struct}->{type_for}->{$col} eq 'enum' ? "CAST(? AS UNSIGNED)" : "?";
push @where, $q->quote($col) . " = " . $val;
}
}
my $col = $index_cols->[$n_index_cols - 1];
my $val = $vals->[-1]; # should only be as many vals as cols
push @where, $q->quote($col) . " >= ?";
my $condition = $tbl->{tbl_struct}->{type_for}->{$col} eq 'enum' ? "CAST(? AS UNSIGNED)" : "?";
push @where, $q->quote($col) . " >= " . $condition;
my $sql = "EXPLAIN SELECT /*!40001 SQL_NO_CACHE */ * "
. "FROM $tbl->{name} FORCE INDEX (" . $q->quote($index) . ") "
@@ -9054,7 +9103,7 @@ sub main {
# (*) Frank: commented them out because it caused infinite loop
# and the mentioned test error doesn't arise
#my $original_error = $EVAL_ERROR;
my $original_error = $EVAL_ERROR;
foreach my $task ( reverse @cleanup_tasks ) {
eval {
@@ -9064,7 +9113,7 @@ sub main {
warn "Error cleaning up: $EVAL_ERROR\n";
}
}
#die $original_error if $original_error; # rethrow original error
die $original_error if $original_error; # rethrow original error
return;
}
);
@@ -9556,6 +9605,7 @@ sub main {
# a single chunk on all slaves. E.g. if a slave is out of sync
# and has a lot more rows than the master, single chunking on the
# master could cause the slave to choke.
my $failed_to_nibble;
if ( $nibble_iter->one_nibble() ) {
PTDEBUG && _d('Getting table row estimate on replicas');
my @too_large;
@@ -9582,10 +9632,16 @@ sub main {
. ($tbl->{chunk_size} * $limit)
. " rows (chunk size=$tbl->{chunk_size}"
. " * chunk size limit=$limit).\n";
die ts($msg);
warn $msg;
$failed_to_nibble = 1;
warn "Switching to nibble\n";
$nibble_iter->switch_to_nibble();
# die ts($msg);
} else {
return 1;
}
}
else { # chunking the table
if (!$nibble_iter->one_nibble()) { # chunking the table
if ( $o->get('check-plan') ) {
my $idx_len = new IndexLength(Quoter => $q);
my ($key_len, $key) = $idx_len->index_length(
@@ -12334,30 +12390,6 @@ duplicate rows and this data will be lost.
This options bypasses confirmation in case of using alter-foreign-keys-method = none , which might break foreign key constraints.
=item --force-concat-enums
The NibbleIterator in Percona Toolkit can detect indexes having ENUM fields and
if the items it has are sorted or not. According to MySQL documentation at
L<https://dev.mysql.com/doc/refman/8.0/en/enum.html>:
ENUM values are sorted based on their index numbers, which depend on the order in
which the enumeration members were listed in the column specification.
For example, 'b' sorts before 'a' for ENUM('b', 'a').
The empty string sorts before nonempty strings, and NULL values sort before all other
enumeration values.
To prevent unexpected results when using the ORDER BY clause on an ENUM column,
use one of these techniques:
- Specify the ENUM list in alphabetic order.
- Make sure that the column is sorted lexically rather than by index number by coding
ORDER BY CAST(col AS CHAR) or ORDER BY CONCAT(col).
The NibbleIterator in Percona Toolkit uses CONCAT(col) but, doing that, adds overhead
since MySQL cannot use the column directly and has to calculate the result of CONCAT
for every row.
To make this scenario vissible to the user, if there are indexes having ENUM fields
with usorted items, it is necessary to specify the C<--force-concat-enums> parameter.
=item --help
Show help and exit.

View File

@@ -955,12 +955,12 @@ collect() {
(echo $ts; df -k) >> "$d/$p-df" &
(echo $ts; netstat -antp) >> "$d/$p-netstat" &
(echo $ts; netstat -s) >> "$d/$p-netstat_s" &
fi
(echo $ts; $CMD_MYSQL $EXT_ARGV -e "SHOW FULL PROCESSLIST\G") \
fi
($CMD_MYSQL $EXT_ARGV -e "SHOW FULL PROCESSLIST\G") \
>> "$d/$p-processlist" &
if [ "$have_lock_waits_table" ]; then
(echo $ts; lock_waits) >>"$d/$p-lock-waits" &
(echo $ts; transactions) >>"$d/$p-transactions" &
(lock_waits) >>"$d/$p-lock-waits" &
(transactions) >>"$d/$p-transactions" &
fi
if [ "${mysql_version}" '>' "5.6" ] && [ $ps_instrumentation_enabled == "yes" ]; then
@@ -968,7 +968,7 @@ collect() {
fi
if [ "${mysql_version}" '>' "5.6" ]; then
(echo $ts; ps_prepared_statements) >> "$d/$p-prepared-statements" &
(ps_prepared_statements) >> "$d/$p-prepared-statements" &
fi
slave_status "$d/$p-slave-status" "${mysql_version}"

View File

@@ -4892,6 +4892,7 @@ sub generate_asc_stmt {
cols => \@cols,
quoter => $q,
is_nullable => $tbl_struct->{is_nullable},
type_for => $tbl_struct->{type_for},
);
$asc_stmt->{boundaries}->{$cmp} = $cmp_where->{where};
}
@@ -4912,6 +4913,7 @@ sub generate_cmp_where {
my @slice = @{$args{slice}};
my @cols = @{$args{cols}};
my $is_nullable = $args{is_nullable};
my $type_for = $args{type_for};
my $type = $args{type};
my $q = $self->{Quoter};
@@ -4928,13 +4930,14 @@ sub generate_cmp_where {
my $ord = $slice[$j];
my $col = $cols[$ord];
my $quo = $q->quote($col);
my $val = ($col && ($type_for->{$col} || '')) eq 'enum' ? "CAST(? AS UNSIGNED)" : "?";
if ( $is_nullable->{$col} ) {
push @clause, "((? IS NULL AND $quo IS NULL) OR ($quo = ?))";
push @clause, "(($val IS NULL AND $quo IS NULL) OR ($quo = $val))";
push @r_slice, $ord, $ord;
push @r_scols, $col, $col;
}
else {
push @clause, "$quo = ?";
push @clause, "$quo = $val";
push @r_slice, $ord;
push @r_scols, $col;
}
@@ -4944,15 +4947,16 @@ sub generate_cmp_where {
my $col = $cols[$ord];
my $quo = $q->quote($col);
my $end = $i == $#slice; # Last clause of the whole group.
my $val = ($col && ($type_for->{$col} || '')) eq 'enum' ? "CAST(? AS UNSIGNED)" : "?";
if ( $is_nullable->{$col} ) {
if ( $type =~ m/=/ && $end ) {
push @clause, "(? IS NULL OR $quo $type ?)";
push @clause, "($val IS NULL OR $quo $type $val)";
}
elsif ( $type =~ m/>/ ) {
push @clause, "((? IS NULL AND $quo IS NOT NULL) OR ($quo $cmp ?))";
push @clause, "(($val IS NULL AND $quo IS NOT NULL) OR ($quo $cmp $val)";
}
else { # If $type =~ m/</ ) {
push @clause, "((? IS NOT NULL AND $quo IS NULL) OR ($quo $cmp ?))";
push @clauses, "(($val IS NOT NULL AND $quo IS NULL) OR ($quo $cmp $val))";
}
push @r_slice, $ord, $ord;
push @r_scols, $col, $col;
@@ -4960,7 +4964,7 @@ sub generate_cmp_where {
else {
push @r_slice, $ord;
push @r_scols, $col;
push @clause, ($type =~ m/=/ && $end ? "$quo $type ?" : "$quo $cmp ?");
push @clause, ($type =~ m/=/ && $end ? "$quo $type $val" : "$quo $cmp $val");
}
push @clauses, '(' . join(' AND ', @clause) . ')';
@@ -6293,7 +6297,6 @@ use strict;
use warnings FATAL => 'all';
use English qw(-no_match_vars);
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
use IndexLength;
use Data::Dumper;
$Data::Dumper::Indent = 1;
@@ -6331,82 +6334,131 @@ sub new {
my @cols = grep { !$ignore_col->{$_} } @$all_cols;
my $self;
if ( $nibble_params->{one_nibble} ) {
my $params = _one_nibble(\%args, \@cols, $where, $tbl, \%comments);
$self = {
%args,
one_nibble => 1,
limit => 0,
nibble_sql => $params->{nibble_sql},
explain_nibble_sql => $params->{explain_nibble_sql},
};
} else {
my $params = _nibble_params($nibble_params, $tbl, \%args, \@cols, $chunk_size, $where, \%comments, $q);
$self = {
%args,
index => $params->{index},
limit => $params->{limit},
first_lb_sql => $params->{first_lb_sql},
last_ub_sql => $params->{last_ub_sql},
ub_sql => $params->{ub_sql},
nibble_sql => $params->{nibble_sql},
explain_first_lb_sql => $params->{explain_first_lb_sql},
explain_ub_sql => $params->{explain_ub_sql},
explain_nibble_sql => $params->{explain_nibble_sql},
resume_lb_sql => $params->{resume_lb_sql},
sql => $params->{sql},
};
}
$self->{row_est} = $nibble_params->{row_est},
$self->{nibbleno} = 0;
$self->{have_rows} = 0;
$self->{rowno} = 0;
$self->{oktonibble} = 1;
$self->{pause_file} = $nibble_params->{pause_file};
$self->{sleep} = $args{sleep} || 60;
$self->{nibble_params} = $nibble_params;
$self->{tbl} = $tbl;
$self->{args} = \%args;
$self->{cols} = \@cols;
$self->{chunk_size} = $chunk_size;
$self->{where} = $where;
$self->{comments} = \%comments;
return bless $self, $class;
}
sub switch_to_nibble {
my $self = shift;
my $params = _nibble_params($self->{nibble_params}, $self->{tbl}, $self->{args}, $self->{cols},
$self->{chunk_size}, $self->{where}, $self->{comments}, $self->{Quoter});
$self->{one_nibble} = 0;
$self->{index} = $params->{index};
$self->{limit} = $params->{limit};
$self->{first_lb_sql} = $params->{first_lb_sql};
$self->{last_ub_sql} = $params->{last_ub_sql};
$self->{ub_sql} = $params->{ub_sql};
$self->{nibble_sql} = $params->{nibble_sql};
$self->{explain_first_lb_sql} = $params->{explain_first_lb_sql};
$self->{explain_ub_sql} = $params->{explain_ub_sql};
$self->{explain_nibble_sql} = $params->{explain_nibble_sql};
$self->{resume_lb_sql} = $params->{resume_lb_sql};
$self->{sql} = $params->{sql};
$self->_get_bounds();
$self->_prepare_sths();
}
sub _one_nibble {
my ($args, $cols, $where, $tbl, $comments) = @_;
my $q = new Quoter();
my $nibble_sql
= ($args{dml} ? "$args{dml} " : "SELECT ")
. ($args{select} ? $args{select}
: join(', ', map { $q->quote($_) } @cols))
= ($args->{dml} ? "$args->{dml} " : "SELECT ")
. ($args->{select} ? $args->{select}
: join(', ', map{ $tbl->{tbl_struct}->{type_for}->{$_} eq 'enum' ?
"CAST(".$q->quote($_)." AS UNSIGNED)" : $q->quote($_) } @$cols))
. " FROM $tbl->{name}"
. ($where ? " WHERE $where" : '')
. ($args{lock_in_share_mode} ? " LOCK IN SHARE MODE" : "")
. " /*$comments{bite}*/";
. ($args->{lock_in_share_mode} ? " LOCK IN SHARE MODE" : "")
. " /*$comments->{bite}*/";
PTDEBUG && _d('One nibble statement:', $nibble_sql);
my $explain_nibble_sql
= "EXPLAIN SELECT "
. ($args{select} ? $args{select}
: join(', ', map { $q->quote($_) } @cols))
. ($args->{select} ? $args->{select}
: join(', ', map{ $tbl->{tbl_struct}->{type_for}->{$_} eq 'enum'
? "CAST(".$q->quote($_)." AS UNSIGNED)" : $q->quote($_) } @$cols))
. " FROM $tbl->{name}"
. ($where ? " WHERE $where" : '')
. ($args{lock_in_share_mode} ? " LOCK IN SHARE MODE" : "")
. " /*explain $comments{bite}*/";
. ($args->{lock_in_share_mode} ? " LOCK IN SHARE MODE" : "")
. " /*explain $comments->{bite}*/";
PTDEBUG && _d('Explain one nibble statement:', $explain_nibble_sql);
$self = {
%args,
return {
one_nibble => 1,
limit => 0,
nibble_sql => $nibble_sql,
explain_nibble_sql => $explain_nibble_sql,
};
}
else {
}
sub _nibble_params {
my ($nibble_params, $tbl, $args, $cols, $chunk_size, $where, $comments, $q) = @_;
my $index = $nibble_params->{index}; # brevity
my $index_cols = $tbl->{tbl_struct}->{keys}->{$index}->{cols};
my $asc = $args{TableNibbler}->generate_asc_stmt(
%args,
my $asc = $args->{TableNibbler}->generate_asc_stmt(
%$args,
tbl_struct => $tbl->{tbl_struct},
index => $index,
n_index_cols => $args{n_chunk_index_cols},
cols => \@cols,
n_index_cols => $args->{n_chunk_index_cols},
cols => $cols,
asc_only => 1,
);
PTDEBUG && _d('Ascend params:', Dumper($asc));
my $force_concat_enums = $o->has('force-concat-enums') && $o->get('force-concat-enums');
my $i=0;
for my $index (@{$index_cols}) {
last if $args{n_chunk_index_cols} && $i >= $args{n_chunk_index_cols};
$i++;
if ($tbl->{tbl_struct}->{type_for}->{$index} eq 'enum') {
if ($tbl->{tbl_struct}->{defs}->{$index} =~ m/enum\s*\((.*?)\)/) {
my @items = split(/,\s*/, $1);
my $sorted = 1; # Asume the items list is sorted to later check if this is true
for (my $i=1; $i < scalar(@items); $i++) {
if ($items[$i-1] gt $items[$i]) {
$sorted = 0;
last;
}
}
if (!$force_concat_enums && !$sorted) {
die "The index " . $index . " in table " . $tbl->{name} .
" has unsorted enum items.\nPlease read the documentation for the --force-concat-enums parameter\n";
}
}
}
}
my $force_concat_enums;
my $from = "$tbl->{name} FORCE INDEX(`$index`)";
my $order_by = join(', ', map { $tbl->{tbl_struct}->{type_for}->{$_} eq 'enum' && $force_concat_enums
? "CONCAT(".$q->quote($_).")" : $q->quote($_)} @{$index_cols});
my $order_by_dec = join(' DESC,', map { $tbl->{tbl_struct}->{type_for}->{$_} eq 'enum' && $force_concat_enums
? "CONCAT(".$q->quote($_).")" : $q->quote($_)} @{$index_cols});
my $order_by = join(', ', map {$q->quote($_)} @{$index_cols});
my $order_by_dec = join(' DESC,', map {$q->quote($_)} @{$index_cols});
my $first_lb_sql
= "SELECT /*!40001 SQL_NO_CACHE */ "
. join(', ', map { $q->quote($_) } @{$asc->{scols}})
. join(', ', map { $tbl->{tbl_struct}->{type_for}->{$_} eq 'enum' ? "CAST(".$q->quote($_)." AS UNSIGNED)" : $q->quote($_)} @{$asc->{scols}})
. " FROM $from"
. ($where ? " WHERE $where" : '')
. " ORDER BY $order_by"
@@ -6415,10 +6467,10 @@ sub new {
PTDEBUG && _d('First lower boundary statement:', $first_lb_sql);
my $resume_lb_sql;
if ( $args{resume} ) {
if ( $args->{resume} ) {
$resume_lb_sql
= "SELECT /*!40001 SQL_NO_CACHE */ "
. join(', ', map { $q->quote($_) } @{$asc->{scols}})
. join(', ', map { $tbl->{tbl_struct}->{type_for}->{$_} eq 'enum' ? "CAST(".$q->quote($_)." AS UNSIGNED)" : $q->quote($_)} @{$asc->{scols}})
. " FROM $from"
. " WHERE " . $asc->{boundaries}->{'>'}
. ($where ? " AND ($where)" : '')
@@ -6430,7 +6482,7 @@ sub new {
my $last_ub_sql
= "SELECT /*!40001 SQL_NO_CACHE */ "
. join(', ', map { $q->quote($_) } @{$asc->{scols}})
. join(', ', map { $tbl->{tbl_struct}->{type_for}->{$_} eq 'enum' ? "CAST(".$q->quote($_)." AS UNSIGNED)" : $q->quote($_)} @{$asc->{scols}})
. " FROM $from"
. ($where ? " WHERE $where" : '')
. " ORDER BY "
@@ -6441,7 +6493,7 @@ sub new {
my $ub_sql
= "SELECT /*!40001 SQL_NO_CACHE */ "
. join(', ', map { $q->quote($_) } @{$asc->{scols}})
. join(', ', map { $tbl->{tbl_struct}->{type_for}->{$_} eq 'enum' ? "CAST(".$q->quote($_)." AS UNSIGNED)" : $q->quote($_)} @{$asc->{scols}})
. " FROM $from"
. " WHERE " . $asc->{boundaries}->{'>='}
. ($where ? " AND ($where)" : '')
@@ -6451,36 +6503,36 @@ sub new {
PTDEBUG && _d('Upper boundary statement:', $ub_sql);
my $nibble_sql
= ($args{dml} ? "$args{dml} " : "SELECT ")
. ($args{select} ? $args{select}
: join(', ', map { $q->quote($_) } @{$asc->{cols}}))
= ($args->{dml} ? "$args->{dml} " : "SELECT ")
. ($args->{select} ? $args->{select}
: join(', ', map { $tbl->{tbl_struct}->{type_for}->{$_} eq 'enum' ? "CAST(".$q->quote($_)." AS UNSIGNED)" : $q->quote($_)} @{$asc->{cols}}))
. " FROM $from"
. " WHERE " . $asc->{boundaries}->{'>='} # lower boundary
. " AND " . $asc->{boundaries}->{'<='} # upper boundary
. ($where ? " AND ($where)" : '')
. ($args{order_by} ? " ORDER BY $order_by" : "")
. ($args{lock_in_share_mode} ? " LOCK IN SHARE MODE" : "")
. " /*$comments{nibble}*/";
. ($args->{order_by} ? " ORDER BY $order_by" : "")
. ($args->{lock_in_share_mode} ? " LOCK IN SHARE MODE" : "")
. " /*$comments->{nibble}*/";
PTDEBUG && _d('Nibble statement:', $nibble_sql);
my $explain_nibble_sql
= "EXPLAIN SELECT "
. ($args{select} ? $args{select}
. ($args->{select} ? $args->{select}
: join(', ', map { $q->quote($_) } @{$asc->{cols}}))
. " FROM $from"
. " WHERE " . $asc->{boundaries}->{'>='} # lower boundary
. " AND " . $asc->{boundaries}->{'<='} # upper boundary
. ($where ? " AND ($where)" : '')
. ($args{order_by} ? " ORDER BY $order_by" : "")
. ($args{lock_in_share_mode} ? " LOCK IN SHARE MODE" : "")
. " /*explain $comments{nibble}*/";
. ($args->{order_by} ? " ORDER BY $order_by" : "")
. ($args->{lock_in_share_mode} ? " LOCK IN SHARE MODE" : "")
. " /*explain $comments->{nibble}*/";
PTDEBUG && _d('Explain nibble statement:', $explain_nibble_sql);
my $limit = $chunk_size - 1;
PTDEBUG && _d('Initial chunk size (LIMIT):', $limit);
$self = {
%args,
my $params = {
one_nibble => 0,
index => $index,
limit => $limit,
first_lb_sql => $first_lb_sql,
@@ -6499,17 +6551,7 @@ sub new {
order_by => $order_by,
},
};
}
$self->{row_est} = $nibble_params->{row_est},
$self->{nibbleno} = 0;
$self->{have_rows} = 0;
$self->{rowno} = 0;
$self->{oktonibble} = 1;
$self->{pause_file} = $nibble_params->{pause_file};
$self->{sleep} = $args{sleep} || 60;
return bless $self, $class;
return $params;
}
sub next {
@@ -6546,7 +6588,6 @@ sub next {
NIBBLE:
while ( $self->{have_rows} || $self->_next_boundaries() ) {
if ($self->{pause_file}) {
while(-f $self->{pause_file}) {
print "Sleeping $self->{sleep} seconds because $self->{pause_file} exists\n";
@@ -6593,6 +6634,7 @@ sub next {
}
$self->{rowno} = 0;
$self->{have_rows} = 0;
}
PTDEBUG && _d('Done nibbling');
@@ -6692,11 +6734,11 @@ sub row_estimate {
sub can_nibble {
my (%args) = @_;
my @required_args = qw(Cxn tbl chunk_size OptionParser TableParser Quoter);
my @required_args = qw(Cxn tbl chunk_size OptionParser TableParser);
foreach my $arg ( @required_args ) {
die "I need a $arg argument" unless $args{$arg};
}
my ($cxn, $tbl, $chunk_size, $o, $q) = @args{@required_args};
my ($cxn, $tbl, $chunk_size, $o) = @args{@required_args};
my $where = $o->has('where') ? $o->get('where') : '';
@@ -6707,28 +6749,13 @@ sub can_nibble {
);
if ( !$where ) {
$mysql_index = undef;
}
$mysql_index = undef;
}
my $chunk_size_limit = $o->get('chunk-size-limit') || 1;
my $one_nibble = !defined $args{one_nibble} || $args{one_nibble}
? $row_est <= $chunk_size * $chunk_size_limit
: 0;
if ($mysql_index) {
my $idx_len = IndexLength->new(Quoter => $q);
my ($key_len, $key) = $idx_len->index_length(
Cxn => $args{Cxn},
tbl => $tbl,
index => $mysql_index,
n_index_cols => $o->get('chunk-index-columns'),
);
if ( !$key || !$key_len || lc($key) ne lc($mysql_index)) {
$one_nibble = 1;
}
}
PTDEBUG && _d('One nibble:', $one_nibble ? 'yes' : 'no');
if ( $args{resume}
@@ -6906,7 +6933,7 @@ sub _get_bounds {
if ( defined $nibble->{lower_boundary}
&& defined $nibble->{upper_boundary} ) {
my $sth = $dbh->prepare($self->{resume_lb_sql});
my @ub = $self->{Quoter}->deserialize_list($nibble->{upper_boundary});
my @ub = split ',', $nibble->{upper_boundary};
PTDEBUG && _d($sth->{Statement}, 'params:', @ub);
$sth->execute(@ub);
$self->{next_lower} = $sth->fetchrow_arrayref();
@@ -7021,7 +7048,7 @@ sub identical_boundaries {
if scalar @$b1 != scalar @$b2; # shouldn't happen
my $n_vals = scalar @$b1;
for my $i ( 0..($n_vals-1) ) {
return 0 if $b1->[$i] ne $b2->[$i]; # diff
return 0 if ($b1->[$i] || '') ne ($b2->[$i] || ''); # diff
}
return 1;
}
@@ -9595,6 +9622,7 @@ use English qw(-no_match_vars);
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
use Data::Dumper;
use Carp;
$Data::Dumper::Indent = 1;
$Data::Dumper::Sortkeys = 1;
$Data::Dumper::Quotekeys = 0;
@@ -9607,7 +9635,7 @@ sub new {
}
my $self = {
Quoter => $args{Quoter},
Quoter => $args{Quoter},
};
return bless $self, $class;
@@ -9659,11 +9687,21 @@ sub _get_first_values {
die "I need a $arg argument" unless $args{$arg};
}
my ($cxn, $tbl, $index, $n_index_cols) = @args{@required_args};
my $q = new Quoter();
my $q = $self->{Quoter};
my $index_struct = $tbl->{tbl_struct}->{keys}->{$index};
my $index_cols = $index_struct->{cols};
my $index_columns = join (', ',
my $index_columns;
eval {
$index_columns = join (', ',
map { $q->quote($_) } @{$index_cols}[0..($n_index_cols - 1)]);
};
if ($EVAL_ERROR) {
confess "$EVAL_ERROR";
}
my @where;
foreach my $col ( @{$index_cols}[0..($n_index_cols - 1)] ) {
@@ -9675,19 +9713,21 @@ sub _get_first_values {
. "WHERE " . join(' AND ', @where)
. " ORDER BY $index_columns "
. "LIMIT 1 /*key_len*/"; # only need 1 row
PTDEBUG && _d("_get_first_values: $sql");
PTDEBUG && _d($sql);
my $vals = $cxn->dbh()->selectrow_arrayref($sql);
return $vals;
}
sub _make_range_query {
my ($self, %args) = @_;
my @required_args = qw(tbl index n_index_cols);
my @required_args = qw(tbl index n_index_cols vals);
foreach my $arg ( @required_args ) {
die "I need a $arg argument" unless $args{$arg};
}
my ($tbl, $index, $n_index_cols, $vals) = @args{@required_args};
my $q = new Quoter();
my $q = $self->{Quoter};
my $index_struct = $tbl->{tbl_struct}->{keys}->{$index};
my $index_cols = $index_struct->{cols};
@@ -9695,12 +9735,15 @@ sub _make_range_query {
if ( $n_index_cols > 1 ) {
foreach my $n ( 0..($n_index_cols - 2) ) {
my $col = $index_cols->[$n];
push @where, $q->quote($col) . " = ?";
my $val = $tbl->{tbl_struct}->{type_for}->{$col} eq 'enum' ? "CAST(? AS UNSIGNED)" : "?";
push @where, $q->quote($col) . " = " . $val;
}
}
my $col = $index_cols->[$n_index_cols - 1];
push @where, $q->quote($col) . " >= ?";
my $val = $vals->[-1]; # should only be as many vals as cols
my $condition = $tbl->{tbl_struct}->{type_for}->{$col} eq 'enum' ? "CAST(? AS UNSIGNED)" : "?";
push @where, $q->quote($col) . " >= " . $condition;
my $sql = "EXPLAIN SELECT /*!40001 SQL_NO_CACHE */ * "
. "FROM $tbl->{name} FORCE INDEX (" . $q->quote($index) . ") "

View File

@@ -6437,6 +6437,7 @@ sub generate_asc_stmt {
cols => \@cols,
quoter => $q,
is_nullable => $tbl_struct->{is_nullable},
type_for => $tbl_struct->{type_for},
);
$asc_stmt->{boundaries}->{$cmp} = $cmp_where->{where};
}
@@ -6457,6 +6458,7 @@ sub generate_cmp_where {
my @slice = @{$args{slice}};
my @cols = @{$args{cols}};
my $is_nullable = $args{is_nullable};
my $type_for = $args{type_for};
my $type = $args{type};
my $q = $self->{Quoter};
@@ -6473,13 +6475,14 @@ sub generate_cmp_where {
my $ord = $slice[$j];
my $col = $cols[$ord];
my $quo = $q->quote($col);
my $val = ($col && ($type_for->{$col} || '')) eq 'enum' ? "CAST(? AS UNSIGNED)" : "?";
if ( $is_nullable->{$col} ) {
push @clause, "((? IS NULL AND $quo IS NULL) OR ($quo = ?))";
push @clause, "(($val IS NULL AND $quo IS NULL) OR ($quo = $val))";
push @r_slice, $ord, $ord;
push @r_scols, $col, $col;
}
else {
push @clause, "$quo = ?";
push @clause, "$quo = $val";
push @r_slice, $ord;
push @r_scols, $col;
}
@@ -6489,15 +6492,16 @@ sub generate_cmp_where {
my $col = $cols[$ord];
my $quo = $q->quote($col);
my $end = $i == $#slice; # Last clause of the whole group.
my $val = ($col && ($type_for->{$col} || '')) eq 'enum' ? "CAST(? AS UNSIGNED)" : "?";
if ( $is_nullable->{$col} ) {
if ( $type =~ m/=/ && $end ) {
push @clause, "(? IS NULL OR $quo $type ?)";
push @clause, "($val IS NULL OR $quo $type $val)";
}
elsif ( $type =~ m/>/ ) {
push @clause, "((? IS NULL AND $quo IS NOT NULL) OR ($quo $cmp ?))";
push @clause, "(($val IS NULL AND $quo IS NOT NULL) OR ($quo $cmp $val)";
}
else { # If $type =~ m/</ ) {
push @clause, "((? IS NOT NULL AND $quo IS NULL) OR ($quo $cmp ?))";
push @clauses, "(($val IS NOT NULL AND $quo IS NULL) OR ($quo $cmp $val))";
}
push @r_slice, $ord, $ord;
push @r_scols, $col, $col;
@@ -6505,7 +6509,7 @@ sub generate_cmp_where {
else {
push @r_slice, $ord;
push @r_scols, $col;
push @clause, ($type =~ m/=/ && $end ? "$quo $type ?" : "$quo $cmp ?");
push @clause, ($type =~ m/=/ && $end ? "$quo $type $val" : "$quo $cmp $val");
}
push @clauses, '(' . join(' AND ', @clause) . ')';

View File

@@ -99,7 +99,7 @@ sub _get_first_values {
}
my ($cxn, $tbl, $index, $n_index_cols) = @args{@required_args};
my $q = $self->{quoter};
my $q = $self->{Quoter};
# Select just the index columns.
my $index_struct = $tbl->{tbl_struct}->{keys}->{$index};
@@ -151,8 +151,8 @@ sub _make_range_query {
# we don't want the last column; that's added below.
foreach my $n ( 0..($n_index_cols - 2) ) {
my $col = $index_cols->[$n];
my $val = $vals->[$n];
push @where, $q->quote($col) . " = ?";
my $val = $tbl->{tbl_struct}->{type_for}->{$col} eq 'enum' ? "CAST(? AS UNSIGNED)" : "?";
push @where, $q->quote($col) . " = " . $val;
}
}
@@ -160,7 +160,8 @@ sub _make_range_query {
# the N left-most index columns.
my $col = $index_cols->[$n_index_cols - 1];
my $val = $vals->[-1]; # should only be as many vals as cols
push @where, $q->quote($col) . " >= ?";
my $condition = $tbl->{tbl_struct}->{type_for}->{$col} eq 'enum' ? "CAST(? AS UNSIGNED)" : "?";
push @where, $q->quote($col) . " >= " . $condition;
my $sql = "EXPLAIN SELECT /*!40001 SQL_NO_CACHE */ * "
. "FROM $tbl->{name} FORCE INDEX (" . $q->quote($index) . ") "

View File

@@ -26,7 +26,6 @@ use strict;
use warnings FATAL => 'all';
use English qw(-no_match_vars);
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
use IndexLength;
use Data::Dumper;
$Data::Dumper::Indent = 1;
@@ -90,92 +89,142 @@ sub new {
my @cols = grep { !$ignore_col->{$_} } @$all_cols;
my $self;
if ( $nibble_params->{one_nibble} ) {
my $params = _one_nibble(\%args, \@cols, $where, $tbl, \%comments);
$self = {
%args,
one_nibble => 1,
limit => 0,
nibble_sql => $params->{nibble_sql},
explain_nibble_sql => $params->{explain_nibble_sql},
};
} else {
my $params = _nibble_params($nibble_params, $tbl, \%args, \@cols, $chunk_size, $where, \%comments, $q);
$self = {
%args,
index => $params->{index},
limit => $params->{limit},
first_lb_sql => $params->{first_lb_sql},
last_ub_sql => $params->{last_ub_sql},
ub_sql => $params->{ub_sql},
nibble_sql => $params->{nibble_sql},
explain_first_lb_sql => $params->{explain_first_lb_sql},
explain_ub_sql => $params->{explain_ub_sql},
explain_nibble_sql => $params->{explain_nibble_sql},
resume_lb_sql => $params->{resume_lb_sql},
sql => $params->{sql},
};
}
$self->{row_est} = $nibble_params->{row_est},
$self->{nibbleno} = 0;
$self->{have_rows} = 0;
$self->{rowno} = 0;
$self->{oktonibble} = 1;
$self->{pause_file} = $nibble_params->{pause_file};
$self->{sleep} = $args{sleep} || 60;
$self->{nibble_params} = $nibble_params;
$self->{tbl} = $tbl;
$self->{args} = \%args;
$self->{cols} = \@cols;
$self->{chunk_size} = $chunk_size;
$self->{where} = $where;
$self->{comments} = \%comments;
return bless $self, $class;
}
sub switch_to_nibble {
my $self = shift;
my $params = _nibble_params($self->{nibble_params}, $self->{tbl}, $self->{args}, $self->{cols},
$self->{chunk_size}, $self->{where}, $self->{comments}, $self->{Quoter});
$self->{one_nibble} = 0;
$self->{index} = $params->{index};
$self->{limit} = $params->{limit};
$self->{first_lb_sql} = $params->{first_lb_sql};
$self->{last_ub_sql} = $params->{last_ub_sql};
$self->{ub_sql} = $params->{ub_sql};
$self->{nibble_sql} = $params->{nibble_sql};
$self->{explain_first_lb_sql} = $params->{explain_first_lb_sql};
$self->{explain_ub_sql} = $params->{explain_ub_sql};
$self->{explain_nibble_sql} = $params->{explain_nibble_sql};
$self->{resume_lb_sql} = $params->{resume_lb_sql};
$self->{sql} = $params->{sql};
$self->_get_bounds();
$self->_prepare_sths();
}
sub _one_nibble {
my ($args, $cols, $where, $tbl, $comments) = @_;
my $q = new Quoter();
# If the chunk size is >= number of rows in table, then we don't
# need to chunk; we can just select all rows, in order, at once.
my $nibble_sql
= ($args{dml} ? "$args{dml} " : "SELECT ")
. ($args{select} ? $args{select}
: join(', ', map { $q->quote($_) } @cols))
= ($args->{dml} ? "$args->{dml} " : "SELECT ")
. ($args->{select} ? $args->{select}
# : join(', ', map { $q->quote($_) } @$cols))
: join(', ', map{ $tbl->{tbl_struct}->{type_for}->{$_} eq 'enum' ?
"CAST(".$q->quote($_)." AS UNSIGNED)" : $q->quote($_) } @$cols))
. " FROM $tbl->{name}"
. ($where ? " WHERE $where" : '')
. ($args{lock_in_share_mode} ? " LOCK IN SHARE MODE" : "")
. " /*$comments{bite}*/";
. ($args->{lock_in_share_mode} ? " LOCK IN SHARE MODE" : "")
. " /*$comments->{bite}*/";
PTDEBUG && _d('One nibble statement:', $nibble_sql);
my $explain_nibble_sql
= "EXPLAIN SELECT "
. ($args{select} ? $args{select}
: join(', ', map { $q->quote($_) } @cols))
. ($args->{select} ? $args->{select}
: join(', ', map{ $tbl->{tbl_struct}->{type_for}->{$_} eq 'enum'
? "CAST(".$q->quote($_)." AS UNSIGNED)" : $q->quote($_) } @$cols))
. " FROM $tbl->{name}"
. ($where ? " WHERE $where" : '')
. ($args{lock_in_share_mode} ? " LOCK IN SHARE MODE" : "")
. " /*explain $comments{bite}*/";
. ($args->{lock_in_share_mode} ? " LOCK IN SHARE MODE" : "")
. " /*explain $comments->{bite}*/";
PTDEBUG && _d('Explain one nibble statement:', $explain_nibble_sql);
$self = {
%args,
return {
one_nibble => 1,
limit => 0,
nibble_sql => $nibble_sql,
explain_nibble_sql => $explain_nibble_sql,
};
}
else {
}
sub _nibble_params {
my ($nibble_params, $tbl, $args, $cols, $chunk_size, $where, $comments, $q) = @_;
my $index = $nibble_params->{index}; # brevity
my $index_cols = $tbl->{tbl_struct}->{keys}->{$index}->{cols};
# Figure out how to nibble the table with the index.
my $asc = $args{TableNibbler}->generate_asc_stmt(
%args,
my $asc = $args->{TableNibbler}->generate_asc_stmt(
%$args,
tbl_struct => $tbl->{tbl_struct},
index => $index,
n_index_cols => $args{n_chunk_index_cols},
cols => \@cols,
n_index_cols => $args->{n_chunk_index_cols},
cols => $cols,
asc_only => 1,
);
PTDEBUG && _d('Ascend params:', Dumper($asc));
# Check if enum fields items are sorted or not.
# If they are sorted we can skip adding CONCAT to improve the queries eficiency.
my $force_concat_enums = $o->has('force-concat-enums') && $o->get('force-concat-enums');
my $i=0;
for my $index (@{$index_cols}) {
last if $args{n_chunk_index_cols} && $i >= $args{n_chunk_index_cols};
$i++;
if ($tbl->{tbl_struct}->{type_for}->{$index} eq 'enum') {
if ($tbl->{tbl_struct}->{defs}->{$index} =~ m/enum\s*\((.*?)\)/) {
my @items = split(/,\s*/, $1);
my $sorted = 1; # Asume the items list is sorted to later check if this is true
for (my $i=1; $i < scalar(@items); $i++) {
if ($items[$i-1] gt $items[$i]) {
$sorted = 0;
last;
}
}
if (!$force_concat_enums && !$sorted) {
die "The index " . $index . " in table " . $tbl->{name} .
" has unsorted enum items.\nPlease read the documentation for the --force-concat-enums parameter\n";
}
}
}
}
my $force_concat_enums;
# Make SQL statements, prepared on first call to next(). FROM and
# ORDER BY are the same for all statements. FORCE IDNEX and ORDER BY
# are needed to ensure deterministic nibbling.
my $from = "$tbl->{name} FORCE INDEX(`$index`)";
my $order_by = join(', ', map { $tbl->{tbl_struct}->{type_for}->{$_} eq 'enum' && $force_concat_enums
? "CONCAT(".$q->quote($_).")" : $q->quote($_)} @{$index_cols});
my $order_by_dec = join(' DESC,', map { $tbl->{tbl_struct}->{type_for}->{$_} eq 'enum' && $force_concat_enums
? "CONCAT(".$q->quote($_).")" : $q->quote($_)} @{$index_cols});
my $order_by = join(', ', map {$q->quote($_)} @{$index_cols});
my $order_by_dec = join(' DESC,', map {$q->quote($_)} @{$index_cols});
# The real first row in the table. Usually we start nibbling from
# this row. Called once in _get_bounds().
my $first_lb_sql
= "SELECT /*!40001 SQL_NO_CACHE */ "
. join(', ', map { $q->quote($_) } @{$asc->{scols}})
. join(', ', map { $tbl->{tbl_struct}->{type_for}->{$_} eq 'enum' ? "CAST(".$q->quote($_)." AS UNSIGNED)" : $q->quote($_)} @{$asc->{scols}})
. " FROM $from"
. ($where ? " WHERE $where" : '')
. " ORDER BY $order_by"
@@ -186,10 +235,10 @@ sub new {
# If we're resuming, this fetches the effective first row, which
# should differ from the real first row. Called once in _get_bounds().
my $resume_lb_sql;
if ( $args{resume} ) {
if ( $args->{resume} ) {
$resume_lb_sql
= "SELECT /*!40001 SQL_NO_CACHE */ "
. join(', ', map { $q->quote($_) } @{$asc->{scols}})
. join(', ', map { $tbl->{tbl_struct}->{type_for}->{$_} eq 'enum' ? "CAST(".$q->quote($_)." AS UNSIGNED)" : $q->quote($_)} @{$asc->{scols}})
. " FROM $from"
. " WHERE " . $asc->{boundaries}->{'>'}
. ($where ? " AND ($where)" : '')
@@ -204,7 +253,7 @@ sub new {
# upper in some cases. Called once in _get_bounds().
my $last_ub_sql
= "SELECT /*!40001 SQL_NO_CACHE */ "
. join(', ', map { $q->quote($_) } @{$asc->{scols}})
. join(', ', map { $tbl->{tbl_struct}->{type_for}->{$_} eq 'enum' ? "CAST(".$q->quote($_)." AS UNSIGNED)" : $q->quote($_)} @{$asc->{scols}})
. " FROM $from"
. ($where ? " WHERE $where" : '')
. " ORDER BY "
@@ -223,7 +272,7 @@ sub new {
# for the next nibble. See _next_boundaries().
my $ub_sql
= "SELECT /*!40001 SQL_NO_CACHE */ "
. join(', ', map { $q->quote($_) } @{$asc->{scols}})
. join(', ', map { $tbl->{tbl_struct}->{type_for}->{$_} eq 'enum' ? "CAST(".$q->quote($_)." AS UNSIGNED)" : $q->quote($_)} @{$asc->{scols}})
. " FROM $from"
. " WHERE " . $asc->{boundaries}->{'>='}
. ($where ? " AND ($where)" : '')
@@ -235,36 +284,36 @@ sub new {
# This statement does the actual nibbling work; its rows are returned
# to the caller via next().
my $nibble_sql
= ($args{dml} ? "$args{dml} " : "SELECT ")
. ($args{select} ? $args{select}
: join(', ', map { $q->quote($_) } @{$asc->{cols}}))
= ($args->{dml} ? "$args->{dml} " : "SELECT ")
. ($args->{select} ? $args->{select}
: join(', ', map { $tbl->{tbl_struct}->{type_for}->{$_} eq 'enum' ? "CAST(".$q->quote($_)." AS UNSIGNED)" : $q->quote($_)} @{$asc->{cols}}))
. " FROM $from"
. " WHERE " . $asc->{boundaries}->{'>='} # lower boundary
. " AND " . $asc->{boundaries}->{'<='} # upper boundary
. ($where ? " AND ($where)" : '')
. ($args{order_by} ? " ORDER BY $order_by" : "")
. ($args{lock_in_share_mode} ? " LOCK IN SHARE MODE" : "")
. " /*$comments{nibble}*/";
. ($args->{order_by} ? " ORDER BY $order_by" : "")
. ($args->{lock_in_share_mode} ? " LOCK IN SHARE MODE" : "")
. " /*$comments->{nibble}*/";
PTDEBUG && _d('Nibble statement:', $nibble_sql);
my $explain_nibble_sql
= "EXPLAIN SELECT "
. ($args{select} ? $args{select}
. ($args->{select} ? $args->{select}
: join(', ', map { $q->quote($_) } @{$asc->{cols}}))
. " FROM $from"
. " WHERE " . $asc->{boundaries}->{'>='} # lower boundary
. " AND " . $asc->{boundaries}->{'<='} # upper boundary
. ($where ? " AND ($where)" : '')
. ($args{order_by} ? " ORDER BY $order_by" : "")
. ($args{lock_in_share_mode} ? " LOCK IN SHARE MODE" : "")
. " /*explain $comments{nibble}*/";
. ($args->{order_by} ? " ORDER BY $order_by" : "")
. ($args->{lock_in_share_mode} ? " LOCK IN SHARE MODE" : "")
. " /*explain $comments->{nibble}*/";
PTDEBUG && _d('Explain nibble statement:', $explain_nibble_sql);
my $limit = $chunk_size - 1;
PTDEBUG && _d('Initial chunk size (LIMIT):', $limit);
$self = {
%args,
my $params = {
one_nibble => 0,
index => $index,
limit => $limit,
first_lb_sql => $first_lb_sql,
@@ -283,17 +332,7 @@ sub new {
order_by => $order_by,
},
};
}
$self->{row_est} = $nibble_params->{row_est},
$self->{nibbleno} = 0;
$self->{have_rows} = 0;
$self->{rowno} = 0;
$self->{oktonibble} = 1;
$self->{pause_file} = $nibble_params->{pause_file};
$self->{sleep} = $args{sleep} || 60;
return bless $self, $class;
return $params;
}
sub next {
@@ -333,7 +372,6 @@ sub next {
# If there's another nibble, fetch the rows within it.
NIBBLE:
while ( $self->{have_rows} || $self->_next_boundaries() ) {
if ($self->{pause_file}) {
while(-f $self->{pause_file}) {
print "Sleeping $self->{sleep} seconds because $self->{pause_file} exists\n";
@@ -488,11 +526,11 @@ sub row_estimate {
sub can_nibble {
my (%args) = @_;
my @required_args = qw(Cxn tbl chunk_size OptionParser TableParser Quoter);
my @required_args = qw(Cxn tbl chunk_size OptionParser TableParser);
foreach my $arg ( @required_args ) {
die "I need a $arg argument" unless $args{$arg};
}
my ($cxn, $tbl, $chunk_size, $o, $q) = @args{@required_args};
my ($cxn, $tbl, $chunk_size, $o) = @args{@required_args};
my $where = $o->has('where') ? $o->get('where') : '';
@@ -503,23 +541,6 @@ sub can_nibble {
where => $where,
);
my $can_get_keys;
if ($mysql_index) {
my $idx_len = IndexLength->new(Quoter => $q);
my ($key_len, $key) = $idx_len->index_length(
Cxn => $args{Cxn},
tbl => $tbl,
index => $mysql_index,
n_index_cols => $o->get('chunk-index-columns'),
);
if ( !$key || !$key_len || lc($key) ne lc($mysql_index)) {
$can_get_keys = 0;
} else {
$can_get_keys = 1;
}
}
# MySQL's chosen index is only something we should prefer
# if --where is used. Else, we can chose our own index
# and disregard the MySQL index from the row estimate.
@@ -539,10 +560,6 @@ sub can_nibble {
my $one_nibble = !defined $args{one_nibble} || $args{one_nibble}
? $row_est <= $chunk_size * $chunk_size_limit
: 0;
if (!$can_get_keys) {
$one_nibble = 1;
}
PTDEBUG && _d('One nibble:', $one_nibble ? 'yes' : 'no');
# Special case: we're resuming and there's no boundaries, so the table
@@ -922,7 +939,7 @@ sub identical_boundaries {
if scalar @$b1 != scalar @$b2; # shouldn't happen
my $n_vals = scalar @$b1;
for my $i ( 0..($n_vals-1) ) {
return 0 if $b1->[$i] ne $b2->[$i]; # diff
return 0 if ($b1->[$i] || '') ne ($b2->[$i] || ''); # diff
}
return 1;
}

View File

@@ -125,6 +125,7 @@ sub generate_asc_stmt {
cols => \@cols,
quoter => $q,
is_nullable => $tbl_struct->{is_nullable},
type_for => $tbl_struct->{type_for},
);
$asc_stmt->{boundaries}->{$cmp} = $cmp_where->{where};
}
@@ -152,6 +153,7 @@ sub generate_cmp_where {
my @slice = @{$args{slice}};
my @cols = @{$args{cols}};
my $is_nullable = $args{is_nullable};
my $type_for = $args{type_for};
my $type = $args{type};
my $q = $self->{Quoter};
@@ -169,13 +171,14 @@ sub generate_cmp_where {
my $ord = $slice[$j];
my $col = $cols[$ord];
my $quo = $q->quote($col);
my $val = ($col && ($type_for->{$col} || '')) eq 'enum' ? "CAST(? AS UNSIGNED)" : "?";
if ( $is_nullable->{$col} ) {
push @clause, "((? IS NULL AND $quo IS NULL) OR ($quo = ?))";
push @clause, "(($val IS NULL AND $quo IS NULL) OR ($quo = $val))";
push @r_slice, $ord, $ord;
push @r_scols, $col, $col;
}
else {
push @clause, "$quo = ?";
push @clause, "$quo = $val";
push @r_slice, $ord;
push @r_scols, $col;
}
@@ -188,15 +191,16 @@ sub generate_cmp_where {
my $col = $cols[$ord];
my $quo = $q->quote($col);
my $end = $i == $#slice; # Last clause of the whole group.
my $val = ($col && ($type_for->{$col} || '')) eq 'enum' ? "CAST(? AS UNSIGNED)" : "?";
if ( $is_nullable->{$col} ) {
if ( $type =~ m/=/ && $end ) {
push @clause, "(? IS NULL OR $quo $type ?)";
push @clause, "($val IS NULL OR $quo $type $val)";
}
elsif ( $type =~ m/>/ ) {
push @clause, "((? IS NULL AND $quo IS NOT NULL) OR ($quo $cmp ?))";
push @clause, "(($val IS NULL AND $quo IS NOT NULL) OR ($quo $cmp $val)";
}
else { # If $type =~ m/</ ) {
push @clause, "((? IS NOT NULL AND $quo IS NULL) OR ($quo $cmp ?))";
push @clauses, "(($val IS NOT NULL AND $quo IS NULL) OR ($quo $cmp $val))";
}
push @r_slice, $ord, $ord;
push @r_scols, $col, $col;
@@ -204,7 +208,7 @@ sub generate_cmp_where {
else {
push @r_slice, $ord;
push @r_scols, $col;
push @clause, ($type =~ m/=/ && $end ? "$quo $type ?" : "$quo $cmp ?");
push @clause, ($type =~ m/=/ && $end ? "$quo $type $val" : "$quo $cmp $val");
}
# Add the clause to the larger WHERE clause.

View File

@@ -1,5 +1,3 @@
AWS_ACCESS_KEY_ID=AKIAJQ2GZPAJ3JZS52HQ
AWS_SECRET_ACCESS_KEY=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
GOCACHE=
GOLANG_DOCKERHUB_TAG=1.10-stretch
TEST_MONGODB_ADMIN_USERNAME=admin

View File

@@ -36,11 +36,10 @@ my $dp = new DSNParser(opts=>$dsn_opts);
my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp);
my $dbh = $sb->get_dbh_for('master');
plan skip_all => 'Cannot connect to sandbox master';
if ( !$dbh ) {
plan skip_all => 'Cannot connect to sandbox master';
} else {
plan tests => 62;
plan tests => 56;
}
my $q = new Quoter();
@@ -897,76 +896,6 @@ is(
"Use non-unique index with highest cardinality (bug 1199591)"
);
$sb->load_file('master', "t/lib/samples/NibbleIterator/enum_keys.sql");
$ni = undef;
eval {
$ni = make_nibble_iter(
db => 'test',
tbl => 't1',
argv => [qw(--databases test --chunk-size 3)],
);
};
like(
$EVAL_ERROR,
qr/The index f3 in table `test`.`t1` has unsorted enum items/,
"PT-1572 Die on unsorted enum items in index",
);
eval {
$ni = make_nibble_iter(
db => 'test',
tbl => 't1',
argv => [qw(--databases test --force-concat-enums --chunk-size 3)],
);
};
like(
$ni->{explain_first_lb_sql},
qr/ORDER BY `f1`, `f2`, CONCAT\(`f3`\)/,
"PT-1572 Use of CONCAT for unsorted ENUM field items without --",
);
eval {
$ni = make_nibble_iter(
db => 'test',
tbl => 't2',
argv => [qw(--databases test --chunk-size 3)],
);
};
is(
$EVAL_ERROR,
'',
"PT-1572 No errors on sorted enum items in index",
);
like(
$ni->{explain_first_lb_sql},
qr/ORDER BY `f1`, `f2`, `f3`/,
"PT-1572 Don't use CONCAT for sorted ENUM field items without --force-concat-enums",
);
eval {
$ni = make_nibble_iter(
db => 'test',
tbl => 't1',
argv => [qw(--databases test --chunk-size 3 --chunk-index-columns 2)],
);
};
is(
$EVAL_ERROR,
'',
"PT-1572 No errors on unsorted enum items in index and --chunk-index-columns",
);
like(
$ni->{explain_first_lb_sql},
qr/ORDER BY `f1`, `f2`, `f3`/,
"PT-1572 Don't use CONCAT for sorted ENUM field items without --force-concat-enums & --chunk-index-columns",
);
# #############################################################################
# Done.
# #############################################################################

View File

@@ -417,17 +417,24 @@ is_deeply(
'>=' => '((`rental_date` > ?) OR (`rental_date` = ? AND '
. '`inventory_id` > ?) OR (`rental_date` = ? AND `inventory_id` '
. '= ? AND (? IS NULL OR `customer_id` >= ?)))',
'>' => '((`rental_date` > ?) OR (`rental_date` = ? AND '
. '`inventory_id` > ?) OR (`rental_date` = ? AND `inventory_id` '
. '= ? AND ((? IS NULL AND `customer_id` IS NOT NULL) '
. 'OR (`customer_id` > ?))))',
'>' => '((`rental_date` > ?) OR (`rental_date` = ? AND `inventory_id` > ?) OR '
. '(`rental_date` = ? AND `inventory_id` = ? AND ((? IS NULL AND `customer_id` IS NOT NULL) '
. 'OR (`customer_id` > ?)))',
# '((`rental_date` > ?) OR (`rental_date` = ? AND '
#. '`inventory_id` > ?) OR (`rental_date` = ? AND `inventory_id` '
# . '= ? AND ((? IS NULL AND `customer_id` IS NOT NULL) '
# . 'OR (`customer_id` > ?))))',
'<=' => '((`rental_date` < ?) OR (`rental_date` = ? AND '
. '`inventory_id` < ?) OR (`rental_date` = ? AND `inventory_id` '
. '= ? AND (? IS NULL OR `customer_id` <= ?)))',
'<' => '((`rental_date` < ?) OR (`rental_date` = ? AND '
. '`inventory_id` < ?) OR (`rental_date` = ? AND `inventory_id` '
. '= ? AND ((? IS NOT NULL AND `customer_id` IS NULL) '
. 'OR (`customer_id` < ?))))',
'<' => '((`rental_date` < ?) OR (`rental_date` = ? AND `inventory_id` < ?) OR '
. '((? IS NOT NULL AND `customer_id` IS NULL) OR (`customer_id` < ?)) OR '
. '(`rental_date` = ? AND `inventory_id` = ?))',
# '((`rental_date` < ?) OR (`rental_date` = ? AND '
#. '`inventory_id` < ?) OR (`rental_date` = ? AND `inventory_id` '
#. '= ? AND ((? IS NOT NULL AND `customer_id` IS NULL) '
#. 'OR (`customer_id` < ?))))',
},
},
'Alternate index on sakila.rental with nullable customer_id',
@@ -460,26 +467,35 @@ is_deeply(
cols => [qw(rental_id rental_date inventory_id customer_id
return_date staff_id last_update)],
index => 'rental_date',
where => '((`rental_date` > ?) OR (`rental_date` = ? AND `inventory_id` > ?)'
. ' OR (`rental_date` = ? AND `inventory_id` = ? AND '
. '((? IS NULL AND `customer_id` IS NOT NULL) OR (`customer_id` > ?))))',
where => '((`rental_date` > ?) OR (`rental_date` = ? AND `inventory_id` > ?) OR '
. '(`rental_date` = ? AND `inventory_id` = ? AND ((? IS NULL AND `customer_id` IS NOT NULL) '
. 'OR (`customer_id` > ?)))',
# '((`rental_date` > ?) OR (`rental_date` = ? AND `inventory_id` > ?)'
#. ' OR (`rental_date` = ? AND `inventory_id` = ? AND '
#. '((? IS NULL AND `customer_id` IS NOT NULL) OR (`customer_id` > ?))))',
slice => [1, 1, 2, 1, 2, 3, 3],
scols => [qw(rental_date rental_date inventory_id rental_date inventory_id customer_id customer_id)],
boundaries => {
'>=' => '((`rental_date` > ?) OR (`rental_date` = ? AND '
. '`inventory_id` > ?) OR (`rental_date` = ? AND `inventory_id` '
. '= ? AND (? IS NULL OR `customer_id` >= ?)))',
'>' => '((`rental_date` > ?) OR (`rental_date` = ? AND '
. '`inventory_id` > ?) OR (`rental_date` = ? AND `inventory_id` '
. '= ? AND ((? IS NULL AND `customer_id` IS NOT NULL) '
. 'OR (`customer_id` > ?))))',
'>' => '((`rental_date` > ?) OR (`rental_date` = ? AND `inventory_id` > ?) OR '
. '(`rental_date` = ? AND `inventory_id` = ? AND ((? IS NULL AND `customer_id` IS NOT NULL) OR '
. '(`customer_id` > ?)))',
# '((`rental_date` > ?) OR (`rental_date` = ? AND '
#. '`inventory_id` > ?) OR (`rental_date` = ? AND `inventory_id` '
#. '= ? AND ((? IS NULL AND `customer_id` IS NOT NULL) '
#. 'OR (`customer_id` > ?))))',
'<=' => '((`rental_date` < ?) OR (`rental_date` = ? AND '
. '`inventory_id` < ?) OR (`rental_date` = ? AND `inventory_id` '
. '= ? AND (? IS NULL OR `customer_id` <= ?)))',
'<' => '((`rental_date` < ?) OR (`rental_date` = ? AND '
. '`inventory_id` < ?) OR (`rental_date` = ? AND `inventory_id` '
. '= ? AND ((? IS NOT NULL AND `customer_id` IS NULL) '
. 'OR (`customer_id` < ?))))',
'<' => '((`rental_date` < ?) OR (`rental_date` = ? AND `inventory_id` < ?) OR '
. '((? IS NOT NULL AND `customer_id` IS NULL) OR (`customer_id` < ?)) OR '
. '(`rental_date` = ? AND `inventory_id` = ?))',
# '((`rental_date` < ?) OR (`rental_date` = ? AND '
#. '`inventory_id` < ?) OR (`rental_date` = ? AND `inventory_id` '
#. '= ? AND ((? IS NOT NULL AND `customer_id` IS NULL) '
#. 'OR (`customer_id` < ?))))',
},
},
'Alternate index on sakila.rental with nullable customer_id and strict ascending',
@@ -500,30 +516,45 @@ is_deeply(
cols => [qw(rental_id rental_date inventory_id customer_id
return_date staff_id last_update)],
index => 'rental_date',
where => '((`rental_date` > ?) OR '
. '(`rental_date` = ? AND ((? IS NULL AND `inventory_id` IS NOT NULL) OR (`inventory_id` > ?)))'
. ' OR (`rental_date` = ? AND ((? IS NULL AND `inventory_id` IS NULL) '
. 'OR (`inventory_id` = ?)) AND `customer_id` >= ?))',
where => '((`rental_date` > ?) OR (`rental_date` = ? AND ((? IS NULL AND `inventory_id` IS NOT NULL)'
. ' OR (`inventory_id` > ?)) OR (`rental_date` = ? AND ((? IS NULL AND `inventory_id` IS NULL) OR '
. '(`inventory_id` = ?)) AND `customer_id` >= ?))',
# '((`rental_date` > ?) OR '
#. '(`rental_date` = ? AND ((? IS NULL AND `inventory_id` IS NOT NULL) OR (`inventory_id` > ?)))'
#. ' OR (`rental_date` = ? AND ((? IS NULL AND `inventory_id` IS NULL) '
#. 'OR (`inventory_id` = ?)) AND `customer_id` >= ?))',
slice => [1, 1, 2, 2, 1, 2, 2, 3],
scols => [qw(rental_date rental_date inventory_id inventory_id
rental_date inventory_id inventory_id customer_id)],
boundaries => {
'>=' => '((`rental_date` > ?) OR (`rental_date` = ? AND '
. '((? IS NULL AND `inventory_id` IS NOT NULL) OR (`inventory_id` '
. '> ?))) OR (`rental_date` = ? AND ((? IS NULL AND `inventory_id` '
. 'IS NULL) OR (`inventory_id` = ?)) AND `customer_id` >= ?))',
'>' => '((`rental_date` > ?) OR (`rental_date` = ? AND ((? IS NULL '
. 'AND `inventory_id` IS NOT NULL) OR (`inventory_id` > ?))) OR '
. '(`rental_date` = ? AND ((? IS NULL AND `inventory_id` IS NULL) '
. 'OR (`inventory_id` = ?)) AND `customer_id` > ?))',
'<=' => '((`rental_date` < ?) OR (`rental_date` = ? AND ((? IS NOT '
. 'NULL AND `inventory_id` IS NULL) OR (`inventory_id` < ?))) OR '
. '(`rental_date` = ? AND ((? IS NULL AND `inventory_id` IS NULL) '
. 'OR (`inventory_id` = ?)) AND `customer_id` <= ?))',
'<' => '((`rental_date` < ?) OR (`rental_date` = ? AND ((? IS NOT '
. 'NULL AND `inventory_id` IS NULL) OR (`inventory_id` < ?))) '
. 'OR (`rental_date` = ? AND ((? IS NULL AND `inventory_id` IS '
. 'NULL) OR (`inventory_id` = ?)) AND `customer_id` < ?))',
'>=' => '((`rental_date` > ?) OR (`rental_date` = ? AND ((? IS NULL AND `inventory_id` IS NOT NULL) OR '
. '(`inventory_id` > ?)) OR (`rental_date` = ? AND ((? IS NULL AND `inventory_id` IS NULL) OR '
. '(`inventory_id` = ?)) AND `customer_id` >= ?))',
# '((`rental_date` > ?) OR (`rental_date` = ? AND '
#. '((? IS NULL AND `inventory_id` IS NOT NULL) OR (`inventory_id` '
#. '> ?))) OR (`rental_date` = ? AND ((? IS NULL AND `inventory_id` '
#. 'IS NULL) OR (`inventory_id` = ?)) AND `customer_id` >= ?))',
'>' => '((`rental_date` > ?) OR (`rental_date` = ? AND ((? IS NULL AND `inventory_id` IS NOT NULL) OR '
. '(`inventory_id` > ?)) OR (`rental_date` = ? AND ((? IS NULL AND `inventory_id` IS NULL) OR '
. '(`inventory_id` = ?)) AND `customer_id` > ?))',
# '((`rental_date` > ?) OR (`rental_date` = ? AND ((? IS NULL '
#. 'AND `inventory_id` IS NOT NULL) OR (`inventory_id` > ?))) OR '
#. '(`rental_date` = ? AND ((? IS NULL AND `inventory_id` IS NULL) '
#. 'OR (`inventory_id` = ?)) AND `customer_id` > ?))',
'<=' => '((`rental_date` < ?) OR ((? IS NOT NULL AND `inventory_id` IS NULL) OR (`inventory_id` < ?)) '
. 'OR (`rental_date` = ?) OR (`rental_date` = ? AND ((? IS NULL AND `inventory_id` IS NULL) OR '
. '(`inventory_id` = ?)) AND `customer_id` <= ?))',
# '((`rental_date` < ?) OR (`rental_date` = ? AND ((? IS NOT '
#. 'NULL AND `inventory_id` IS NULL) OR (`inventory_id` < ?))) OR '
#. '(`rental_date` = ? AND ((? IS NULL AND `inventory_id` IS NULL) '
#. 'OR (`inventory_id` = ?)) AND `customer_id` <= ?))',
'<' => '((`rental_date` < ?) OR ((? IS NOT NULL AND `inventory_id` IS NULL) OR (`inventory_id` < ?)) '
. 'OR (`rental_date` = ?) OR (`rental_date` = ? AND ((? IS NULL AND `inventory_id` IS NULL) OR '
. '(`inventory_id` = ?)) AND `customer_id` < ?))',
# '((`rental_date` < ?) OR (`rental_date` = ? AND ((? IS NOT '
#. 'NULL AND `inventory_id` IS NULL) OR (`inventory_id` < ?))) '
#. 'OR (`rental_date` = ? AND ((? IS NULL AND `inventory_id` IS '
#. 'NULL) OR (`inventory_id` = ?)) AND `customer_id` < ?))',
},
},
'Alternate index on sakila.rental with nullable inventory_id',
@@ -540,30 +571,45 @@ is_deeply(
cols => [qw(rental_id rental_date inventory_id customer_id
return_date staff_id last_update)],
index => 'rental_date',
where => '((`rental_date` > ?) OR '
. '(`rental_date` = ? AND ((? IS NULL AND `inventory_id` IS NOT NULL) OR (`inventory_id` > ?)))'
. ' OR (`rental_date` = ? AND ((? IS NULL AND `inventory_id` IS NULL) '
. 'OR (`inventory_id` = ?)) AND `customer_id` > ?))',
where => '((`rental_date` > ?) OR (`rental_date` = ? AND ((? IS NULL AND `inventory_id` IS NOT NULL) OR '
. '(`inventory_id` > ?)) OR (`rental_date` = ? AND ((? IS NULL AND `inventory_id` IS NULL) OR '
. '(`inventory_id` = ?)) AND `customer_id` > ?))',
# '((`rental_date` > ?) OR '
#. '(`rental_date` = ? AND ((? IS NULL AND `inventory_id` IS NOT NULL) OR (`inventory_id` > ?)))'
#. ' OR (`rental_date` = ? AND ((? IS NULL AND `inventory_id` IS NULL) '
#. 'OR (`inventory_id` = ?)) AND `customer_id` > ?))',
slice => [1, 1, 2, 2, 1, 2, 2, 3],
scols => [qw(rental_date rental_date inventory_id inventory_id
rental_date inventory_id inventory_id customer_id)],
boundaries => {
'>=' => '((`rental_date` > ?) OR (`rental_date` = ? AND '
. '((? IS NULL AND `inventory_id` IS NOT NULL) OR (`inventory_id` '
. '> ?))) OR (`rental_date` = ? AND ((? IS NULL AND `inventory_id` '
. 'IS NULL) OR (`inventory_id` = ?)) AND `customer_id` >= ?))',
'>' => '((`rental_date` > ?) OR (`rental_date` = ? AND ((? IS NULL '
. 'AND `inventory_id` IS NOT NULL) OR (`inventory_id` > ?))) OR '
. '(`rental_date` = ? AND ((? IS NULL AND `inventory_id` IS NULL) '
. 'OR (`inventory_id` = ?)) AND `customer_id` > ?))',
'<=' => '((`rental_date` < ?) OR (`rental_date` = ? AND ((? IS NOT '
. 'NULL AND `inventory_id` IS NULL) OR (`inventory_id` < ?))) OR '
. '(`rental_date` = ? AND ((? IS NULL AND `inventory_id` IS NULL) '
. 'OR (`inventory_id` = ?)) AND `customer_id` <= ?))',
'<' => '((`rental_date` < ?) OR (`rental_date` = ? AND ((? IS NOT '
. 'NULL AND `inventory_id` IS NULL) OR (`inventory_id` < ?))) '
. 'OR (`rental_date` = ? AND ((? IS NULL AND `inventory_id` IS '
. 'NULL) OR (`inventory_id` = ?)) AND `customer_id` < ?))',
'>=' => '((`rental_date` > ?) OR (`rental_date` = ? AND ((? IS NULL AND `inventory_id` IS NOT NULL) OR '
. '(`inventory_id` > ?)) OR (`rental_date` = ? AND ((? IS NULL AND `inventory_id` IS NULL) OR '
. '(`inventory_id` = ?)) AND `customer_id` >= ?))',
# '((`rental_date` > ?) OR (`rental_date` = ? AND '
#. '((? IS NULL AND `inventory_id` IS NOT NULL) OR (`inventory_id` '
#. '> ?))) OR (`rental_date` = ? AND ((? IS NULL AND `inventory_id` '
#. 'IS NULL) OR (`inventory_id` = ?)) AND `customer_id` >= ?))',
'>' => '((`rental_date` > ?) OR (`rental_date` = ? AND ((? IS NULL AND `inventory_id` IS NOT NULL) OR '
. '(`inventory_id` > ?)) OR (`rental_date` = ? AND ((? IS NULL AND `inventory_id` IS NULL) OR '
. '(`inventory_id` = ?)) AND `customer_id` > ?))',
# '((`rental_date` > ?) OR (`rental_date` = ? AND ((? IS NULL '
#. 'AND `inventory_id` IS NOT NULL) OR (`inventory_id` > ?))) OR '
#. '(`rental_date` = ? AND ((? IS NULL AND `inventory_id` IS NULL) '
#. 'OR (`inventory_id` = ?)) AND `customer_id` > ?))',
'<=' => '((`rental_date` < ?) OR ((? IS NOT NULL AND `inventory_id` IS NULL) OR (`inventory_id` < ?)) OR '
. '(`rental_date` = ?) OR (`rental_date` = ? AND ((? IS NULL AND `inventory_id` IS NULL) OR '
. '(`inventory_id` = ?)) AND `customer_id` <= ?))',
# '((`rental_date` < ?) OR (`rental_date` = ? AND ((? IS NOT '
#. 'NULL AND `inventory_id` IS NULL) OR (`inventory_id` < ?))) OR '
#. '(`rental_date` = ? AND ((? IS NULL AND `inventory_id` IS NULL) '
#. 'OR (`inventory_id` = ?)) AND `customer_id` <= ?))',
'<' => '((`rental_date` < ?) OR ((? IS NOT NULL AND `inventory_id` IS NULL) OR (`inventory_id` < ?)) OR '
. '(`rental_date` = ?) OR (`rental_date` = ? AND ((? IS NULL AND `inventory_id` IS NULL) OR '
. '(`inventory_id` = ?)) AND `customer_id` < ?))',
#'((`rental_date` < ?) OR (`rental_date` = ? AND ((? IS NOT '
#. 'NULL AND `inventory_id` IS NULL) OR (`inventory_id` < ?))) '
#. 'OR (`rental_date` = ? AND ((? IS NULL AND `inventory_id` IS '
#. 'NULL) OR (`inventory_id` = ?)) AND `customer_id` < ?))',
},
},
'Alternate index on sakila.rental with nullable inventory_id and strict ascending',

View File

@@ -94,7 +94,6 @@ sub check_ids {
my $n_updated = $ids->{updated} ? ($ids->{updated} =~ tr/,//) : 0;
my $n_deleted = $ids->{deleted} ? ($ids->{deleted} =~ tr/,//) : 0;
my $n_inserted = $ids->{inserted} ? ($ids->{inserted} =~ tr/,//) : 0;
warn "n_inser $n_inserted";
# "1,1"=~tr/,// returns 1 but is 2 values
$n_updated++ if $ids->{updated};
@@ -179,7 +178,7 @@ start_query_table(qw(pt_osc t id));
sub { pt_online_schema_change::main(
"$master_dsn,D=pt_osc,t=t",
qw(--set-vars innodb_lock_wait_timeout=5),
qw(--print --execute --chunk-size 100 --alter ENGINE=InnoDB)) },
qw(--print --execute --chunk-size 100 --alter ENGINE=InnoDB --no-check-plan)) },
stderr => 1,
);

View File

@@ -177,12 +177,14 @@ sub test_alter_table {
) or $fail = 1;
# Rows in the original and new table should be identical.
my $query = "SELECT $cols FROM $table ORDER BY `$pk_col`";
my $new_rows = $master_dbh->selectall_arrayref("SELECT $cols FROM $table ORDER BY `$pk_col`");
my $should_diag;
is_deeply(
$new_rows,
$orig_rows,
"$name rows"
) or $fail = 1;
) or $fail=1;
if ( grep { $_ eq '--preserve-triggers' } @$cmds ) {
my $new_triggers = $master_dbh->selectall_arrayref($triggers_sql);
@@ -341,6 +343,8 @@ sub test_alter_table {
my $db_flavor = VersionParser->new($master_dbh)->flavor();
if ( $db_flavor =~ m/XtraDB Cluster/ ) {
diag('====================================================================================================');
diag($db_flavor);
test_alter_table(
name => "Basic no fks --dry-run",
table => "pt_osc.t",

View File

@@ -50,8 +50,6 @@ $sb->load_file('master', "$sample/long_fk_constraints.sql");
qw(--execute)) },
);
warn $output;
my $query = <<_SQL;
SELECT TABLE_NAME, CONSTRAINT_NAME
FROM information_schema.KEY_COLUMN_USAGE
@@ -62,8 +60,11 @@ ORDER BY TABLE_NAME, CONSTRAINT_NAME
_SQL
my $constraints = $master_dbh->selectall_arrayref($query);
# why we need to sort? Depending on the MySQL version and the characters set, the ORDER BY clause
# in the query will return different values so, it is better to rely on our own sorted results.
my @sorted_constraints = sort { @$a[0].@$a[1] cmp @$b[0].@$b[1] } @$constraints;
is_deeply(
$constraints,
\@sorted_constraints,
[
[ 'Table1', '__fkey1a' ],
[ 'Table1', '__fkey_SALES_RECURRING_PROFILE_CUSTOMER_CUSTOMER_ENTITY_ENTITY_I' ],

View File

@@ -0,0 +1,96 @@
#!/usr/bin/env perl
BEGIN {
die "The PERCONA_TOOLKIT_BRANCH environment variable is not set.\n"
unless $ENV{PERCONA_TOOLKIT_BRANCH} && -d $ENV{PERCONA_TOOLKIT_BRANCH};
unshift @INC, "$ENV{PERCONA_TOOLKIT_BRANCH}/lib";
};
use strict;
use warnings FATAL => 'all';
use threads;
use threads::shared;
use Thread::Semaphore;
use English qw(-no_match_vars);
use Test::More;
use Data::Dumper;
use PerconaTest;
use Sandbox;
use SqlModes;
use File::Temp qw/ tempdir /;
if ($sandbox_version lt '5.7') {
plan skip_all => 'This test needs MySQL 5.7+';
} else {
plan tests => 3;
}
require "$trunk/bin/pt-online-schema-change";
my $dp = new DSNParser(opts=>$dsn_opts);
my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp);
my $dbh = $sb->get_dbh_for('master');
my $dsn = $sb->dsn_for("master");
my $slave_dbh = $sb->get_dbh_for('slave1');
# The sandbox servers run with lock_wait_timeout=3 and it's not dynamic
# so we need to specify --set-vars innodb_lock_wait_timeout=3 else the
# tool will die.
my @args = (qw(--set-vars innodb_lock_wait_timeout=3));
my $output;
my $exit_status;
my $master_port = $sb->port_for('master');
my $num_rows = 5000;
$sb->load_file('master', "t/pt-online-schema-change/samples/pt-1757.sql");
diag(`util/mysql_random_data_load --host=127.0.0.1 --port=$master_port --user=msandbox --password=msandbox test t1 $num_rows`);
# Let's alter the stats to force this scenario:
# 1) On master, we are going to put 100 as the number of rows in the table. This will make osc to try to run in one chunk
# 2) On the slave, we are going to put the real number of rows. This will cause a fallback to nibble and pt-osc should call
# NibbleIterator->switch_to_nibble()
$dbh->do('SET @@SESSION.sql_log_bin=0');
$dbh->do('update mysql.innodb_table_stats set n_rows=100 where table_name="t1"');
$dbh->do('update mysql.innodb_index_stats set stat_value=100 where stat_description in("id") and table_name="t1"');
$dbh->do('SET @@SESSION.sql_log_bin=1');
$slave_dbh->do('SET @@SESSION.sql_log_bin=0');
$slave_dbh->do("update mysql.innodb_table_stats set n_rows=$num_rows where table_name='t1'");
$slave_dbh->do("update mysql.innodb_index_stats set stat_value=$num_rows where stat_description in('id') and table_name='t1'");
$slave_dbh->do('SET @@SESSION.sql_log_bin=1');
($output, $exit_status) = full_output(
sub { pt_online_schema_change::main(@args, "$dsn,D=test,t=t1",
'--execute', '--alter', "ADD COLUMN new_col INT NOT NULL DEFAULT 1",
'--chunk-size', '25',
),
},
stderr => 1,
);
is(
$exit_status,
0,
"Altered OK status",
);
# The WHERE clause here is important as a double check that the table was altered and new_col exists
my $rows = $dbh->selectrow_arrayref("SELECT COUNT(*) FROM test.t1 WHERE new_col = 1");
is(
$rows->[0],
$num_rows,
"Correct rows count"
) or diag(Dumper($rows));
# #############################################################################
# Done.
# #############################################################################
$sb->wipe_clean($dbh);
ok($sb->ok(), "Sandbox servers") or BAIL_OUT(__FILE__ . " broke the sandbox");
done_testing;

View File

@@ -22,7 +22,6 @@ my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp);
my $master_dbh = $sb->get_dbh_for('master');
my $vp = VersionParser->new($master_dbh);
warn Data::Dumper::Dumper($vp);
if ($vp->cmp('8.0.14') > -1 && $vp->flavor() !~ m/maria/i) {
plan skip_all => 'Cannot run this test under the current MySQL version';
@@ -65,11 +64,14 @@ my $query = <<__SQL;
and CONSTRAINT_NAME LIKE '%fkey%'
ORDER BY TABLE_NAME, CONSTRAINT_NAME
__SQL
my $constraints = $master_dbh->selectall_arrayref($query);
my $constraints = $master_dbh->selectall_arrayref($query);
# why we need to sort? Depending on the MySQL version and the characters set, the ORDER BY clause
# in the query will return different values so, it is better to rely on our own sorted results.
my @sorted_constraints = sort { @$a[0].@$a[1] cmp @$b[0].@$b[1] } @$constraints;
is_deeply(
$constraints,
\@sorted_constraints,
[
['Table1', '__fkey1a'],
['Table1', '_fkey1b'],
@@ -79,8 +81,6 @@ is_deeply(
"First run adds or removes underscore from constraint names, accordingly"
);
# run second time: we expect constraint names to be prefixed with one underscore
# if they havre't one, and to remove 2 if they have 2
($output, $exit_status) = full_output(
@@ -91,10 +91,10 @@ is_deeply(
);
$constraints = $master_dbh->selectall_arrayref("SELECT TABLE_NAME, CONSTRAINT_NAME FROM information_schema.KEY_COLUMN_USAGE WHERE table_schema='bug1215587' and (TABLE_NAME='Table1' OR TABLE_NAME='Table2') and CONSTRAINT_NAME LIKE '%fkey%' ORDER BY TABLE_NAME, CONSTRAINT_NAME");
@sorted_constraints = sort { @$a[0].@$a[1] cmp @$b[0].@$b[1] } @$constraints; # read above why we need to sort
is_deeply(
$constraints,
\@sorted_constraints,
[
['Table1', '__fkey1b'],
['Table1', 'fkey1a'],
@@ -113,10 +113,10 @@ is_deeply(
);
$constraints = $master_dbh->selectall_arrayref("SELECT TABLE_NAME, CONSTRAINT_NAME FROM information_schema.KEY_COLUMN_USAGE WHERE table_schema='bug1215587' and (TABLE_NAME='Table1' OR TABLE_NAME='Table2') and CONSTRAINT_NAME LIKE '%fkey%' ORDER BY TABLE_NAME, CONSTRAINT_NAME");
@sorted_constraints = sort { @$a[0].@$a[1] cmp @$b[0].@$b[1] } @$constraints; # read above why we need to sort
is_deeply(
$constraints,
\@sorted_constraints,
[
['Table1', '_fkey1a'],
['Table1', 'fkey1b'],

View File

@@ -0,0 +1,15 @@
DROP DATABASE IF EXISTS `test`;
CREATE DATABASE `test`;
CREATE TABLE `test`.`t1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`f2` char(32) DEFAULT NULL,
`f3` ENUM('red','green','blue'),
PRIMARY KEY (`id`),
UNIQUE KEY `c` (`f2`)
) ENGINE=InnoDB
AUTO_INCREMENT=0
DEFAULT CHARSET=utf8
STATS_AUTO_RECALC = 0
STATS_PERSISTENT = 1;

View File

@@ -116,7 +116,7 @@ like(
$output,
qr/Failed to find a unique new table name/,
"Doesn't try forever to find a new table name"
);
) or diag($output);
# #############################################################################
# Done.

Binary file not shown.