From 940ca17d718efe98b7c4c9d812dfa289c28de722 Mon Sep 17 00:00:00 2001 From: Daniel Nichter Date: Tue, 3 Apr 2012 10:59:37 -0700 Subject: [PATCH] Update modules in pt-query-advisor, replacing MySQLDump with TableParser. --- bin/pt-query-advisor | 614 +++++++++++++++++-------------------------- 1 file changed, 245 insertions(+), 369 deletions(-) diff --git a/bin/pt-query-advisor b/bin/pt-query-advisor index 93a48027..0e2416a9 100755 --- a/bin/pt-query-advisor +++ b/bin/pt-query-advisor @@ -141,12 +141,14 @@ sub parse_options { sub as_string { my ( $self, $dsn, $props ) = @_; return $dsn unless ref $dsn; - my %allowed = $props ? map { $_=>1 } @$props : (); + my @keys = $props ? @$props : sort keys %$dsn; return join(',', - map { "$_=" . ($_ eq 'p' ? '...' : $dsn->{$_}) } - grep { defined $dsn->{$_} && $self->{opts}->{$_} } - grep { !$props || $allowed{$_} } - sort keys %$dsn ); + map { "$_=" . ($_ eq 'p' ? '...' : $dsn->{$_}) } + grep { + exists $self->{opts}->{$_} + && exists $dsn->{$_} + && defined $dsn->{$_} + } @keys); } sub usage { @@ -1315,7 +1317,7 @@ sub _parse_size { $opt->{value} = ($pre || '') . $num; } else { - $self->save_error("Invalid size for --$opt->{long}"); + $self->save_error("Invalid size for --$opt->{long}: $val"); } return; } @@ -1460,6 +1462,48 @@ sub join_quote { 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; } # ########################################################################### @@ -2354,8 +2398,25 @@ sub fingerprint { $query =~ s/\\["']//g; # 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 chomp $query; # Kill trailing whitespace $query =~ tr[ \n\t\r\f][ ]s; # Collapse whitespace @@ -3435,7 +3496,7 @@ sub get_rules { foreach my $pred ( @$where ) { my $val = $pred->{right_arg}; 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; }, @@ -3494,7 +3555,7 @@ sub get_rules { my $cols = $event->{query_struct}->{columns}; return unless $cols; foreach my $col ( @$cols ) { - return 0 if $col->{col} eq '*'; + return 0 if $col->{col} eq '*' && !$col->{func}; } return; }, @@ -4067,6 +4128,21 @@ my $column_ident = qr/(?: (?:$ident_alias)? # optional alias )/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 { my ( $class, %args ) = @_; my $self = { @@ -4085,6 +4161,7 @@ sub parse { |REPLACE |SELECT |UPDATE + |CREATE )/xi; $query = $self->clean_query($query); @@ -4108,6 +4185,11 @@ sub parse { @subqueries = $self->remove_subqueries($query); $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 $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 { my ( $self, $from ) = @_; return unless $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 $ansi_join = qr/(?> \s+ @@ -4348,13 +4450,10 @@ sub parse_from { $join->{condition} = lc $join_condition_verb; if ( $join->{condition} eq 'on' ) { - my $where = $self->parse_where($join_condition_value); - $join->{where} = $where; + $join->{where} = $self->parse_where($join_condition_value, $funcs); } else { # USING - $join_condition_value =~ s/^\s*\(//; - $join_condition_value =~ s/\)\s*$//; - $join->{columns} = $self->_parse_csv($join_condition_value); + $join->{columns} = $self->_parse_csv(shift @$using_cols); } } elsif ( $thing =~ m/(?:,|JOIN)/i ) { @@ -4433,13 +4532,13 @@ sub parse_table_reference { } sub parse_where { - my ( $self, $where ) = @_; + my ( $self, $where, $functions ) = @_; return unless $where; PTDEBUG && _d("Parsing WHERE", $where); my $op_symbol = qr/ (?: - <= + <=(?:>)? |>= |<> |!= @@ -4546,6 +4645,11 @@ sub parse_where { $val = lc $val; } + if ( $functions ) { + $col = shift @$functions if $col =~ m/__FUNC\d+__/; + $val = shift @$functions if $val =~ m/__FUNC\d+__/; + } + push @predicates, { predicate => $conj, left_arg => $col, @@ -4813,6 +4917,44 @@ sub remove_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 { my ( $self, $idents ) = @_; return unless $idents; @@ -4854,6 +4996,14 @@ sub parse_identifier { return unless $type && $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_parts = map { s/`//g; $_; } split /[.]/, $ident; 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)); return \%ident_struct; } @@ -4942,311 +5096,6 @@ sub _d { # 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 # This package is a copy without comments from the original. The original @@ -5278,19 +5127,58 @@ sub new { 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 { my ( $self, $ddl, $opts ) = @_; 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 `/ ) { die "Cannot parse table definition; is ANSI quoting " @@ -5597,41 +5485,31 @@ sub remove_auto_increment { return $ddl; } -sub remove_secondary_indexes { - my ( $self, $ddl ) = @_; - my $sec_indexes_ddl; - my $tbl_struct = $self->parse($ddl); - - if ( ($tbl_struct->{engine} || '') =~ m/InnoDB/i ) { - my $clustered_key = $tbl_struct->{clustered_key}; - $clustered_key ||= ''; - - 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; +sub get_table_status { + my ( $self, $dbh, $db, $like ) = @_; + my $q = $self->{Quoter}; + my $sql = "SHOW TABLE STATUS FROM " . $q->quote($db); + my @params; + if ( $like ) { + $sql .= ' LIKE ?'; + push @params, $like; } - else { - PTDEBUG && _d('Not removing secondary indexes from', - $tbl_struct->{engine}, 'table'); + PTDEBUG && _d($sql, @params); + my $sth = $dbh->prepare($sql); + eval { $sth->execute(@params); }; + if ($EVAL_ERROR) { + PTDEBUG && _d($EVAL_ERROR); + return; } - - return $ddl, $sec_indexes_ddl, $tbl_struct; + 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; + return @tables; } sub _d { @@ -6091,7 +5969,6 @@ sub main { my $qr = new QueryRewriter( QueryParser => $qp ); my $sp = new SQLParser(); my $tp = new TableParser(Quoter => $q); - my $du = new MySQLDump(); my %common_modules = ( DSNParser => $dp, Quoter => $q, @@ -6100,7 +5977,6 @@ sub main { QueryRewriter => $qr, SQLParser => $sp, TableParser => $tp, - MySQLDump => $du, ); # ######################################################################### @@ -6286,8 +6162,8 @@ sub main { if ( !$tbl_structs->{$db}->{$tbl} ) { my $tbl_struct; eval { - $tbl_struct - = $tp->parse($du->get_create_table($dbh, $q, $db, $tbl)); + $tbl_struct = $tp->parse( + $tp->get_create_table($dbh, $db, $tbl)); }; if ( $EVAL_ERROR ) { warn "Failed to get SHOW CREATE TABLE for $db.$tbl: "