Update modules in pt-query-advisor, replacing MySQLDump with TableParser.

This commit is contained in:
Daniel Nichter
2012-04-03 10:59:37 -07:00
parent 10a92ac6da
commit 940ca17d71

View File

@@ -141,12 +141,14 @@ sub parse_options {
sub as_string { sub as_string {
my ( $self, $dsn, $props ) = @_; my ( $self, $dsn, $props ) = @_;
return $dsn unless ref $dsn; return $dsn unless ref $dsn;
my %allowed = $props ? map { $_=>1 } @$props : (); my @keys = $props ? @$props : sort keys %$dsn;
return join(',', return join(',',
map { "$_=" . ($_ eq 'p' ? '...' : $dsn->{$_}) } map { "$_=" . ($_ eq 'p' ? '...' : $dsn->{$_}) }
grep { defined $dsn->{$_} && $self->{opts}->{$_} } grep {
grep { !$props || $allowed{$_} } exists $self->{opts}->{$_}
sort keys %$dsn ); && exists $dsn->{$_}
&& defined $dsn->{$_}
} @keys);
} }
sub usage { sub usage {
@@ -1315,7 +1317,7 @@ sub _parse_size {
$opt->{value} = ($pre || '') . $num; $opt->{value} = ($pre || '') . $num;
} }
else { else {
$self->save_error("Invalid size for --$opt->{long}"); $self->save_error("Invalid size for --$opt->{long}: $val");
} }
return; return;
} }
@@ -1460,6 +1462,48 @@ sub join_quote {
return $db ? "$db.$tbl" : $tbl; return $db ? "$db.$tbl" : $tbl;
} }
sub serialize_list {
my ( $self, @args ) = @_;
return unless @args;
return $args[0] if @args == 1 && !defined $args[0];
die "Cannot serialize multiple values with undef/NULL"
if grep { !defined $_ } @args;
return join ',', map { quotemeta } @args;
}
sub deserialize_list {
my ( $self, $string ) = @_;
return $string unless defined $string;
my @escaped_parts = $string =~ /
\G # Start of string, or end of previous match.
( # Each of these is an element in the original list.
[^\\,]* # Anything not a backslash or a comma
(?: # When we get here, we found one of the above.
\\. # A backslash followed by something so we can continue
[^\\,]* # Same as above.
)* # Repeat zero of more times.
)
, # Comma dividing elements
/sxgc;
push @escaped_parts, pos($string) ? substr( $string, pos($string) ) : $string;
my @unescaped_parts = map {
my $part = $_;
my $char_class = utf8::is_utf8($part) # If it's a UTF-8 string,
? qr/(?=\p{ASCII})\W/ # We only care about non-word
: qr/(?=\p{ASCII})\W|[\x{80}-\x{FF}]/; # Otherwise,
$part =~ s/\\($char_class)/$1/g;
$part;
} @escaped_parts;
return @unescaped_parts;
}
1; 1;
} }
# ########################################################################### # ###########################################################################
@@ -2354,8 +2398,25 @@ sub fingerprint {
$query =~ s/\\["']//g; # quoted strings $query =~ s/\\["']//g; # quoted strings
$query =~ s/".*?"/?/sg; # quoted strings $query =~ s/".*?"/?/sg; # quoted strings
$query =~ s/'.*?'/?/sg; # quoted strings $query =~ s/'.*?'/?/sg; # quoted strings
$query =~ s/[0-9+-][0-9a-f.xb+-]*/?/g;# Anything vaguely resembling numbers
$query =~ s/[xb.+-]\?/?/g; # Clean up leftovers if ( $self->{match_md5_checksums} ) {
$query =~ s/([._-])[a-f0-9]{32}/$1?/g;
}
if ( !$self->{match_embedded_numbers} ) {
$query =~ s/[0-9+-][0-9a-f.xb+-]*/?/g;
}
else {
$query =~ s/\b[0-9+-][0-9a-f.xb+-]*/?/g;
}
if ( $self->{match_md5_checksums} ) {
$query =~ s/[xb+-]\?/?/g;
}
else {
$query =~ s/[xb.+-]\?/?/g;
}
$query =~ s/\A\s+//; # Chop off leading whitespace $query =~ s/\A\s+//; # Chop off leading whitespace
chomp $query; # Kill trailing whitespace chomp $query; # Kill trailing whitespace
$query =~ tr[ \n\t\r\f][ ]s; # Collapse whitespace $query =~ tr[ \n\t\r\f][ ]s; # Collapse whitespace
@@ -3435,7 +3496,7 @@ sub get_rules {
foreach my $pred ( @$where ) { foreach my $pred ( @$where ) {
my $val = $pred->{right_arg}; my $val = $pred->{right_arg};
next unless $val; next unless $val;
return 0 if $val =~ m/^\d+$/ && $orderby_col{lc $pred->{left_arg}}; return 0 if $val =~ m/^\d+$/ && $orderby_col{lc($pred->{left_arg} || '')};
} }
return; return;
}, },
@@ -3494,7 +3555,7 @@ sub get_rules {
my $cols = $event->{query_struct}->{columns}; my $cols = $event->{query_struct}->{columns};
return unless $cols; return unless $cols;
foreach my $col ( @$cols ) { foreach my $col ( @$cols ) {
return 0 if $col->{col} eq '*'; return 0 if $col->{col} eq '*' && !$col->{func};
} }
return; return;
}, },
@@ -4067,6 +4128,21 @@ my $column_ident = qr/(?:
(?:$ident_alias)? # optional alias (?:$ident_alias)? # optional alias
)/xo; )/xo;
my $function_ident = qr/
\b
(
\w+ # function name
\( # opening parenthesis
[^\)]+ # function args, if any
\) # closing parenthesis
)
/x;
my %ignore_function = (
INDEX => 1,
KEY => 1,
);
sub new { sub new {
my ( $class, %args ) = @_; my ( $class, %args ) = @_;
my $self = { my $self = {
@@ -4085,6 +4161,7 @@ sub parse {
|REPLACE |REPLACE
|SELECT |SELECT
|UPDATE |UPDATE
|CREATE
)/xi; )/xi;
$query = $self->clean_query($query); $query = $self->clean_query($query);
@@ -4108,6 +4185,11 @@ sub parse {
@subqueries = $self->remove_subqueries($query); @subqueries = $self->remove_subqueries($query);
$query = shift @subqueries; $query = shift @subqueries;
} }
elsif ( $type eq 'create' && $query =~ m/\s+SELECT/ ) {
PTDEBUG && _d('CREATE..SELECT');
($subqueries[0]->{query}) = $query =~ m/\s+(SELECT .+)/;
$query =~ s/\s+SELECT.+//;
}
my $parse_func = "parse_$type"; my $parse_func = "parse_$type";
my $struct = $self->$parse_func($query); my $struct = $self->$parse_func($query);
@@ -4316,11 +4398,31 @@ sub parse_update {
} }
sub parse_create {
my ($self, $query) = @_;
my ($obj, $name) = $query =~ m/
(\S+)\s+
(?:IF NOT EXISTS\s+)?
(\S+)
/xi;
return {
object => lc $obj,
name => $name,
unknown => undef,
};
}
sub parse_from { sub parse_from {
my ( $self, $from ) = @_; my ( $self, $from ) = @_;
return unless $from; return unless $from;
PTDEBUG && _d('Parsing FROM', $from); PTDEBUG && _d('Parsing FROM', $from);
my $using_cols;
($from, $using_cols) = $self->remove_using_columns($from);
my $funcs;
($from, $funcs) = $self->remove_functions($from);
my $comma_join = qr/(?>\s*,\s*)/; my $comma_join = qr/(?>\s*,\s*)/;
my $ansi_join = qr/(?> my $ansi_join = qr/(?>
\s+ \s+
@@ -4348,13 +4450,10 @@ sub parse_from {
$join->{condition} = lc $join_condition_verb; $join->{condition} = lc $join_condition_verb;
if ( $join->{condition} eq 'on' ) { if ( $join->{condition} eq 'on' ) {
my $where = $self->parse_where($join_condition_value); $join->{where} = $self->parse_where($join_condition_value, $funcs);
$join->{where} = $where;
} }
else { # USING else { # USING
$join_condition_value =~ s/^\s*\(//; $join->{columns} = $self->_parse_csv(shift @$using_cols);
$join_condition_value =~ s/\)\s*$//;
$join->{columns} = $self->_parse_csv($join_condition_value);
} }
} }
elsif ( $thing =~ m/(?:,|JOIN)/i ) { elsif ( $thing =~ m/(?:,|JOIN)/i ) {
@@ -4433,13 +4532,13 @@ sub parse_table_reference {
} }
sub parse_where { sub parse_where {
my ( $self, $where ) = @_; my ( $self, $where, $functions ) = @_;
return unless $where; return unless $where;
PTDEBUG && _d("Parsing WHERE", $where); PTDEBUG && _d("Parsing WHERE", $where);
my $op_symbol = qr/ my $op_symbol = qr/
(?: (?:
<= <=(?:>)?
|>= |>=
|<> |<>
|!= |!=
@@ -4546,6 +4645,11 @@ sub parse_where {
$val = lc $val; $val = lc $val;
} }
if ( $functions ) {
$col = shift @$functions if $col =~ m/__FUNC\d+__/;
$val = shift @$functions if $val =~ m/__FUNC\d+__/;
}
push @predicates, { push @predicates, {
predicate => $conj, predicate => $conj,
left_arg => $col, left_arg => $col,
@@ -4813,6 +4917,44 @@ sub remove_subqueries {
return $query, @subqueries; return $query, @subqueries;
} }
sub remove_using_columns {
my ($self, $from) = @_;
return unless $from;
PTDEBUG && _d('Removing cols from USING clauses');
my $using = qr/
\bUSING
\s*
\(
([^\)]+)
\)
/xi;
my @cols;
$from =~ s/$using/push @cols, $1; "USING ($#cols)"/eg;
PTDEBUG && _d('FROM:', $from, Dumper(\@cols));
return $from, \@cols;
}
sub replace_function {
my ($func, $funcs) = @_;
my ($func_name) = $func =~ m/^(\w+)/;
if ( !$ignore_function{uc $func_name} ) {
my $n = scalar @$funcs;
push @$funcs, $func;
return "__FUNC${n}__";
}
return $func;
}
sub remove_functions {
my ($self, $clause) = @_;
return unless $clause;
PTDEBUG && _d('Removing functions from clause:', $clause);
my @funcs;
$clause =~ s/$function_ident/replace_function($1, \@funcs)/eg;
PTDEBUG && _d('Function-stripped clause:', $clause, Dumper(\@funcs));
return $clause, \@funcs;
}
sub parse_identifiers { sub parse_identifiers {
my ( $self, $idents ) = @_; my ( $self, $idents ) = @_;
return unless $idents; return unless $idents;
@@ -4854,6 +4996,14 @@ sub parse_identifier {
return unless $type && $ident; return unless $type && $ident;
PTDEBUG && _d("Parsing", $type, "identifier:", $ident); PTDEBUG && _d("Parsing", $type, "identifier:", $ident);
my ($func, $expr);
if ( $ident =~ m/^\w+\(/ ) { # Function like MIN(col)
($func, $expr) = $ident =~ m/^(\w+)\(([^\)]*)\)/;
PTDEBUG && _d('Function', $func, 'arg', $expr);
return { col => $ident } unless $expr; # NOW()
$ident = $expr; # col from MAX(col)
}
my %ident_struct; my %ident_struct;
my @ident_parts = map { s/`//g; $_; } split /[.]/, $ident; my @ident_parts = map { s/`//g; $_; } split /[.]/, $ident;
if ( @ident_parts == 3 ) { if ( @ident_parts == 3 ) {
@@ -4888,6 +5038,10 @@ sub parse_identifier {
} }
} }
if ( $func ) {
$ident_struct{func} = uc $func;
}
PTDEBUG && _d($type, "identifier struct:", Dumper(\%ident_struct)); PTDEBUG && _d($type, "identifier struct:", Dumper(\%ident_struct));
return \%ident_struct; return \%ident_struct;
} }
@@ -4942,311 +5096,6 @@ sub _d {
# End SQLParser package # End SQLParser package
# ########################################################################### # ###########################################################################
# ###########################################################################
# MySQLDump 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/MySQLDump.pm
# t/lib/MySQLDump.t
# See https://launchpad.net/percona-toolkit for more information.
# ###########################################################################
{
package MySQLDump;
use strict;
use warnings FATAL => 'all';
use English qw(-no_match_vars);
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
( our $before = <<'EOF') =~ s/^ //gm;
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
EOF
( our $after = <<'EOF') =~ s/^ //gm;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
EOF
sub new {
my ( $class, %args ) = @_;
my $self = {
cache => 0, # Afaik no script uses this cache any longer because
};
return bless $self, $class;
}
sub dump {
my ( $self, $dbh, $quoter, $db, $tbl, $what ) = @_;
if ( $what eq 'table' ) {
my $ddl = $self->get_create_table($dbh, $quoter, $db, $tbl);
return unless $ddl;
if ( $ddl->[0] eq 'table' ) {
return $before
. 'DROP TABLE IF EXISTS ' . $quoter->quote($tbl) . ";\n"
. $ddl->[1] . ";\n";
}
else {
return 'DROP TABLE IF EXISTS ' . $quoter->quote($tbl) . ";\n"
. '/*!50001 DROP VIEW IF EXISTS '
. $quoter->quote($tbl) . "*/;\n/*!50001 "
. $self->get_tmp_table($dbh, $quoter, $db, $tbl) . "*/;\n";
}
}
elsif ( $what eq 'triggers' ) {
my $trgs = $self->get_triggers($dbh, $quoter, $db, $tbl);
if ( $trgs && @$trgs ) {
my $result = $before . "\nDELIMITER ;;\n";
foreach my $trg ( @$trgs ) {
if ( $trg->{sql_mode} ) {
$result .= qq{/*!50003 SET SESSION SQL_MODE='$trg->{sql_mode}' */;;\n};
}
$result .= "/*!50003 CREATE */ ";
if ( $trg->{definer} ) {
my ( $user, $host )
= map { s/'/''/g; "'$_'"; }
split('@', $trg->{definer}, 2);
$result .= "/*!50017 DEFINER=$user\@$host */ ";
}
$result .= sprintf("/*!50003 TRIGGER %s %s %s ON %s\nFOR EACH ROW %s */;;\n\n",
$quoter->quote($trg->{trigger}),
@{$trg}{qw(timing event)},
$quoter->quote($trg->{table}),
$trg->{statement});
}
$result .= "DELIMITER ;\n\n/*!50003 SET SESSION SQL_MODE=\@OLD_SQL_MODE */;\n\n";
return $result;
}
else {
return undef;
}
}
elsif ( $what eq 'view' ) {
my $ddl = $self->get_create_table($dbh, $quoter, $db, $tbl);
return '/*!50001 DROP TABLE IF EXISTS ' . $quoter->quote($tbl) . "*/;\n"
. '/*!50001 DROP VIEW IF EXISTS ' . $quoter->quote($tbl) . "*/;\n"
. '/*!50001 ' . $ddl->[1] . "*/;\n";
}
else {
die "You didn't say what to dump.";
}
}
sub _use_db {
my ( $self, $dbh, $quoter, $new ) = @_;
if ( !$new ) {
PTDEBUG && _d('No new DB to use');
return;
}
my $sql = 'USE ' . $quoter->quote($new);
PTDEBUG && _d($dbh, $sql);
$dbh->do($sql);
return;
}
sub get_create_table {
my ( $self, $dbh, $quoter, $db, $tbl ) = @_;
if ( !$self->{cache} || !$self->{tables}->{$db}->{$tbl} ) {
my $sql = '/*!40101 SET @OLD_SQL_MODE := @@SQL_MODE, '
. q{@@SQL_MODE := REPLACE(REPLACE(@@SQL_MODE, 'ANSI_QUOTES', ''), ',,', ','), }
. '@OLD_QUOTE := @@SQL_QUOTE_SHOW_CREATE, '
. '@@SQL_QUOTE_SHOW_CREATE := 1 */';
PTDEBUG && _d($sql);
eval { $dbh->do($sql); };
PTDEBUG && $EVAL_ERROR && _d($EVAL_ERROR);
$self->_use_db($dbh, $quoter, $db);
$sql = "SHOW CREATE TABLE " . $quoter->quote($db, $tbl);
PTDEBUG && _d($sql);
my $href;
eval { $href = $dbh->selectrow_hashref($sql); };
if ( $EVAL_ERROR ) {
warn "Failed to $sql. The table may be damaged.\nError: $EVAL_ERROR";
return;
}
$sql = '/*!40101 SET @@SQL_MODE := @OLD_SQL_MODE, '
. '@@SQL_QUOTE_SHOW_CREATE := @OLD_QUOTE */';
PTDEBUG && _d($sql);
$dbh->do($sql);
my ($key) = grep { m/create table/i } keys %$href;
if ( $key ) {
PTDEBUG && _d('This table is a base table');
$self->{tables}->{$db}->{$tbl} = [ 'table', $href->{$key} ];
}
else {
PTDEBUG && _d('This table is a view');
($key) = grep { m/create view/i } keys %$href;
$self->{tables}->{$db}->{$tbl} = [ 'view', $href->{$key} ];
}
}
return $self->{tables}->{$db}->{$tbl};
}
sub get_columns {
my ( $self, $dbh, $quoter, $db, $tbl ) = @_;
PTDEBUG && _d('Get columns for', $db, $tbl);
if ( !$self->{cache} || !$self->{columns}->{$db}->{$tbl} ) {
$self->_use_db($dbh, $quoter, $db);
my $sql = "SHOW COLUMNS FROM " . $quoter->quote($db, $tbl);
PTDEBUG && _d($sql);
my $cols = $dbh->selectall_arrayref($sql, { Slice => {} });
$self->{columns}->{$db}->{$tbl} = [
map {
my %row;
@row{ map { lc $_ } keys %$_ } = values %$_;
\%row;
} @$cols
];
}
return $self->{columns}->{$db}->{$tbl};
}
sub get_tmp_table {
my ( $self, $dbh, $quoter, $db, $tbl ) = @_;
my $result = 'CREATE TABLE ' . $quoter->quote($tbl) . " (\n";
$result .= join(",\n",
map { ' ' . $quoter->quote($_->{field}) . ' ' . $_->{type} }
@{$self->get_columns($dbh, $quoter, $db, $tbl)});
$result .= "\n)";
PTDEBUG && _d($result);
return $result;
}
sub get_triggers {
my ( $self, $dbh, $quoter, $db, $tbl ) = @_;
if ( !$self->{cache} || !$self->{triggers}->{$db} ) {
$self->{triggers}->{$db} = {};
my $sql = '/*!40101 SET @OLD_SQL_MODE := @@SQL_MODE, '
. q{@@SQL_MODE := REPLACE(REPLACE(@@SQL_MODE, 'ANSI_QUOTES', ''), ',,', ','), }
. '@OLD_QUOTE := @@SQL_QUOTE_SHOW_CREATE, '
. '@@SQL_QUOTE_SHOW_CREATE := 1 */';
PTDEBUG && _d($sql);
eval { $dbh->do($sql); };
PTDEBUG && $EVAL_ERROR && _d($EVAL_ERROR);
$sql = "SHOW TRIGGERS FROM " . $quoter->quote($db);
PTDEBUG && _d($sql);
my $sth = $dbh->prepare($sql);
$sth->execute();
if ( $sth->rows ) {
my $trgs = $sth->fetchall_arrayref({});
foreach my $trg (@$trgs) {
my %trg;
@trg{ map { lc $_ } keys %$trg } = values %$trg;
push @{ $self->{triggers}->{$db}->{ $trg{table} } }, \%trg;
}
}
$sql = '/*!40101 SET @@SQL_MODE := @OLD_SQL_MODE, '
. '@@SQL_QUOTE_SHOW_CREATE := @OLD_QUOTE */';
PTDEBUG && _d($sql);
$dbh->do($sql);
}
if ( $tbl ) {
return $self->{triggers}->{$db}->{$tbl};
}
return values %{$self->{triggers}->{$db}};
}
sub get_databases {
my ( $self, $dbh, $quoter, $like ) = @_;
if ( !$self->{cache} || !$self->{databases} || $like ) {
my $sql = 'SHOW DATABASES';
my @params;
if ( $like ) {
$sql .= ' LIKE ?';
push @params, $like;
}
my $sth = $dbh->prepare($sql);
PTDEBUG && _d($sql, @params);
$sth->execute( @params );
my @dbs = map { $_->[0] } @{$sth->fetchall_arrayref()};
$self->{databases} = \@dbs unless $like;
return @dbs;
}
return @{$self->{databases}};
}
sub get_table_status {
my ( $self, $dbh, $quoter, $db, $like ) = @_;
if ( !$self->{cache} || !$self->{table_status}->{$db} || $like ) {
my $sql = "SHOW TABLE STATUS FROM " . $quoter->quote($db);
my @params;
if ( $like ) {
$sql .= ' LIKE ?';
push @params, $like;
}
PTDEBUG && _d($sql, @params);
my $sth = $dbh->prepare($sql);
$sth->execute(@params);
my @tables = @{$sth->fetchall_arrayref({})};
@tables = map {
my %tbl; # Make a copy with lowercased keys
@tbl{ map { lc $_ } keys %$_ } = values %$_;
$tbl{engine} ||= $tbl{type} || $tbl{comment};
delete $tbl{type};
\%tbl;
} @tables;
$self->{table_status}->{$db} = \@tables unless $like;
return @tables;
}
return @{$self->{table_status}->{$db}};
}
sub get_table_list {
my ( $self, $dbh, $quoter, $db, $like ) = @_;
if ( !$self->{cache} || !$self->{table_list}->{$db} || $like ) {
my $sql = "SHOW /*!50002 FULL*/ TABLES FROM " . $quoter->quote($db);
my @params;
if ( $like ) {
$sql .= ' LIKE ?';
push @params, $like;
}
PTDEBUG && _d($sql, @params);
my $sth = $dbh->prepare($sql);
$sth->execute(@params);
my @tables = @{$sth->fetchall_arrayref()};
@tables = map {
my %tbl = (
name => $_->[0],
engine => ($_->[1] || '') eq 'VIEW' ? 'VIEW' : '',
);
\%tbl;
} @tables;
$self->{table_list}->{$db} = \@tables unless $like;
return @tables;
}
return @{$self->{table_list}->{$db}};
}
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 MySQLDump package
# ###########################################################################
# ########################################################################### # ###########################################################################
# TableParser package # TableParser package
# This package is a copy without comments from the original. The original # This package is a copy without comments from the original. The original
@@ -5278,19 +5127,58 @@ sub new {
return bless $self, $class; return bless $self, $class;
} }
sub get_create_table {
my ( $self, $dbh, $db, $tbl ) = @_;
die "I need a dbh parameter" unless $dbh;
die "I need a db parameter" unless $db;
die "I need a tbl parameter" unless $tbl;
my $q = $self->{Quoter};
my $new_sql_mode
= '/*!40101 SET @OLD_SQL_MODE := @@SQL_MODE, '
. q{@@SQL_MODE := REPLACE(REPLACE(@@SQL_MODE, 'ANSI_QUOTES', ''), ',,', ','), }
. '@OLD_QUOTE := @@SQL_QUOTE_SHOW_CREATE, '
. '@@SQL_QUOTE_SHOW_CREATE := 1 */';
my $old_sql_mode = '/*!40101 SET @@SQL_MODE := @OLD_SQL_MODE, '
. '@@SQL_QUOTE_SHOW_CREATE := @OLD_QUOTE */';
PTDEBUG && _d($new_sql_mode);
eval { $dbh->do($new_sql_mode); };
PTDEBUG && $EVAL_ERROR && _d($EVAL_ERROR);
my $use_sql = 'USE ' . $q->quote($db);
PTDEBUG && _d($dbh, $use_sql);
$dbh->do($use_sql);
my $show_sql = "SHOW CREATE TABLE " . $q->quote($db, $tbl);
PTDEBUG && _d($show_sql);
my $href;
eval { $href = $dbh->selectrow_hashref($show_sql); };
if ( $EVAL_ERROR ) {
PTDEBUG && _d($EVAL_ERROR);
PTDEBUG && _d($old_sql_mode);
$dbh->do($old_sql_mode);
return;
}
PTDEBUG && _d($old_sql_mode);
$dbh->do($old_sql_mode);
my ($key) = grep { m/create (?:table|view)/i } keys %$href;
if ( !$key ) {
die "Error: no 'Create Table' or 'Create View' in result set from "
. "$show_sql: " . Dumper($href);
}
return $href->{$key};
}
sub parse { sub parse {
my ( $self, $ddl, $opts ) = @_; my ( $self, $ddl, $opts ) = @_;
return unless $ddl; return unless $ddl;
if ( ref $ddl eq 'ARRAY' ) {
if ( lc $ddl->[0] eq 'table' ) {
$ddl = $ddl->[1];
}
else {
return {
engine => 'VIEW',
};
}
}
if ( $ddl !~ m/CREATE (?:TEMPORARY )?TABLE `/ ) { if ( $ddl !~ m/CREATE (?:TEMPORARY )?TABLE `/ ) {
die "Cannot parse table definition; is ANSI quoting " die "Cannot parse table definition; is ANSI quoting "
@@ -5597,41 +5485,31 @@ sub remove_auto_increment {
return $ddl; return $ddl;
} }
sub remove_secondary_indexes { sub get_table_status {
my ( $self, $ddl ) = @_; my ( $self, $dbh, $db, $like ) = @_;
my $sec_indexes_ddl; my $q = $self->{Quoter};
my $tbl_struct = $self->parse($ddl); my $sql = "SHOW TABLE STATUS FROM " . $q->quote($db);
my @params;
if ( ($tbl_struct->{engine} || '') =~ m/InnoDB/i ) { if ( $like ) {
my $clustered_key = $tbl_struct->{clustered_key}; $sql .= ' LIKE ?';
$clustered_key ||= ''; push @params, $like;
my @sec_indexes = map {
my $key_def = $_->{ddl};
$key_def =~ s/([\(\)])/\\$1/g;
$ddl =~ s/\s+$key_def//i;
my $key_ddl = "ADD $_->{ddl}";
$key_ddl .= ',' unless $key_ddl =~ m/,$/;
$key_ddl;
}
grep { $_->{name} ne $clustered_key }
values %{$tbl_struct->{keys}};
PTDEBUG && _d('Secondary indexes:', Dumper(\@sec_indexes));
if ( @sec_indexes ) {
$sec_indexes_ddl = join(' ', @sec_indexes);
$sec_indexes_ddl =~ s/,$//;
}
$ddl =~ s/,(\n\) )/$1/s;
} }
else { PTDEBUG && _d($sql, @params);
PTDEBUG && _d('Not removing secondary indexes from', my $sth = $dbh->prepare($sql);
$tbl_struct->{engine}, 'table'); eval { $sth->execute(@params); };
if ($EVAL_ERROR) {
PTDEBUG && _d($EVAL_ERROR);
return;
} }
my @tables = @{$sth->fetchall_arrayref({})};
return $ddl, $sec_indexes_ddl, $tbl_struct; @tables = map {
my %tbl; # Make a copy with lowercased keys
@tbl{ map { lc $_ } keys %$_ } = values %$_;
$tbl{engine} ||= $tbl{type} || $tbl{comment};
delete $tbl{type};
\%tbl;
} @tables;
return @tables;
} }
sub _d { sub _d {
@@ -6091,7 +5969,6 @@ sub main {
my $qr = new QueryRewriter( QueryParser => $qp ); my $qr = new QueryRewriter( QueryParser => $qp );
my $sp = new SQLParser(); my $sp = new SQLParser();
my $tp = new TableParser(Quoter => $q); my $tp = new TableParser(Quoter => $q);
my $du = new MySQLDump();
my %common_modules = ( my %common_modules = (
DSNParser => $dp, DSNParser => $dp,
Quoter => $q, Quoter => $q,
@@ -6100,7 +5977,6 @@ sub main {
QueryRewriter => $qr, QueryRewriter => $qr,
SQLParser => $sp, SQLParser => $sp,
TableParser => $tp, TableParser => $tp,
MySQLDump => $du,
); );
# ######################################################################### # #########################################################################
@@ -6286,8 +6162,8 @@ sub main {
if ( !$tbl_structs->{$db}->{$tbl} ) { if ( !$tbl_structs->{$db}->{$tbl} ) {
my $tbl_struct; my $tbl_struct;
eval { eval {
$tbl_struct $tbl_struct = $tp->parse(
= $tp->parse($du->get_create_table($dbh, $q, $db, $tbl)); $tp->get_create_table($dbh, $db, $tbl));
}; };
if ( $EVAL_ERROR ) { if ( $EVAL_ERROR ) {
warn "Failed to get SHOW CREATE TABLE for $db.$tbl: " warn "Failed to get SHOW CREATE TABLE for $db.$tbl: "