mirror of
https://github.com/percona/percona-toolkit.git
synced 2025-09-24 13:25:01 +00:00
Update modules in pt-query-advisor, replacing MySQLDump with TableParser.
This commit is contained in:
@@ -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: "
|
||||
|
Reference in New Issue
Block a user