From 1f4d99665f474b75e8108fa06dacc3572ed47235 Mon Sep 17 00:00:00 2001 From: "Brian Fraser fraserb@gmail.com" <> Date: Tue, 5 Jun 2012 14:41:36 -0300 Subject: [PATCH] pt-table-sync: Bail out if --lock-and-rename and MySQL <= 5.5 --- bin/pt-table-sync | 180 ++++++++++++++++++++++++++++-- t/pt-table-sync/lock_and_rename.t | 5 +- 2 files changed, 174 insertions(+), 11 deletions(-) diff --git a/bin/pt-table-sync b/bin/pt-table-sync index fc5f897e..8363a563 100755 --- a/bin/pt-table-sync +++ b/bin/pt-table-sync @@ -1536,20 +1536,81 @@ sub new { sub parse { my ( $self, $str ) = @_; - my $result = sprintf('%03d%03d%03d', $str =~ m/(\d+)/g); + my @version_parts = $str =~ m/(\d+)/g; + @version_parts = map { $_ || 0 } @version_parts[0..2]; + my $result = sprintf('%03d%03d%03d', @version_parts); PTDEBUG && _d($str, 'parses to', $result); return $result; } +sub version_cmp { + my ($self, $dbh, $target, $cmp) = @_; + my $version = $self->version($dbh); + my $result; + + if ( $cmp eq 'ge' ) { + $result = $self->{$dbh} ge $self->parse($target) ? 1 : 0; + } + elsif ( $cmp eq 'gt' ) { + $result = $self->{$dbh} gt $self->parse($target) ? 1 : 0; + } + elsif ( $cmp eq 'eq' ) { + $result = $self->{$dbh} eq $self->parse($target) ? 1 : 0; + } + elsif ( $cmp eq 'ne' ) { + $result = $self->{$dbh} ne $self->parse($target) ? 1 : 0; + } + elsif ( $cmp eq 'lt' ) { + $result = $self->{$dbh} lt $self->parse($target) ? 1 : 0; + } + elsif ( $cmp eq 'le' ) { + $result = $self->{$dbh} le $self->parse($target) ? 1 : 0; + } + else { + die "Asked for an unknown comparizon: $cmp" + } + + PTDEBUG && _d($self->{$dbh}, $cmp, $target, ':', $result); + return $result; +} + sub version_ge { my ( $self, $dbh, $target ) = @_; + return $self->version_cmp($dbh, $target, 'ge'); +} + +sub version_gt { + my ( $self, $dbh, $target ) = @_; + return $self->version_cmp($dbh, $target, 'gt'); +} + +sub version_eq { + my ( $self, $dbh, $target ) = @_; + return $self->version_cmp($dbh, $target, 'eq'); +} + +sub version_ne { + my ( $self, $dbh, $target ) = @_; + return $self->version_cmp($dbh, $target, 'ne'); +} + +sub version_lt { + my ( $self, $dbh, $target ) = @_; + return $self->version_cmp($dbh, $target, 'lt'); +} + +sub version_le { + my ( $self, $dbh, $target ) = @_; + return $self->version_cmp($dbh, $target, 'le'); +} + +sub version { + my ( $self, $dbh ) = @_; if ( !$self->{$dbh} ) { $self->{$dbh} = $self->parse( $dbh->selectrow_array('SELECT VERSION()')); } - my $result = $self->{$dbh} ge $self->parse($target) ? 1 : 0; - PTDEBUG && _d($self->{$dbh}, 'ge', $target, ':', $result); - return $result; + return $self->{$dbh}; } sub innodb_version { @@ -7215,6 +7276,7 @@ use constant PTDEBUG => $ENV{PTDEBUG} || 0; use Time::Local qw(timegm timelocal); use Digest::MD5 qw(md5_hex); +use B qw(); require Exporter; our @ISA = qw(Exporter); @@ -7232,6 +7294,7 @@ our @EXPORT_OK = qw( any_unix_timestamp make_checksum crc32 + encode_json ); our $mysql_ts = qr/(\d\d)(\d\d)(\d\d) +(\d+):(\d+):(\d+)(\.\d+)?/; @@ -7439,6 +7502,96 @@ sub crc32 { return $crc ^ 0xFFFFFFFF; } +my $got_json = eval { require JSON }; +sub encode_json { + return JSON::encode_json(@_) if $got_json; + my ( $data ) = @_; + return (object_to_json($data) || ''); +} + + +sub object_to_json { + my ($obj) = @_; + my $type = ref($obj); + + if($type eq 'HASH'){ + return hash_to_json($obj); + } + elsif($type eq 'ARRAY'){ + return array_to_json($obj); + } + else { + return value_to_json($obj); + } +} + +sub hash_to_json { + my ($obj) = @_; + my @res; + for my $k ( sort { $a cmp $b } keys %$obj ) { + push @res, string_to_json( $k ) + . ":" + . ( object_to_json( $obj->{$k} ) || value_to_json( $obj->{$k} ) ); + } + return '{' . ( @res ? join( ",", @res ) : '' ) . '}'; +} + +sub array_to_json { + my ($obj) = @_; + my @res; + + for my $v (@$obj) { + push @res, object_to_json($v) || value_to_json($v); + } + + return '[' . ( @res ? join( ",", @res ) : '' ) . ']'; +} + +sub value_to_json { + my ($value) = @_; + + return 'null' if(!defined $value); + + my $b_obj = B::svref_2object(\$value); # for round trip problem + my $flags = $b_obj->FLAGS; + return $value # as is + if $flags & ( B::SVp_IOK | B::SVp_NOK ) and !( $flags & B::SVp_POK ); # SvTYPE is IV or NV? + + my $type = ref($value); + + if( !$type ) { + return string_to_json($value); + } + else { + return 'null'; + } + +} + +my %esc = ( + "\n" => '\n', + "\r" => '\r', + "\t" => '\t', + "\f" => '\f', + "\b" => '\b', + "\"" => '\"', + "\\" => '\\\\', + "\'" => '\\\'', +); + +sub string_to_json { + my ($arg) = @_; + + $arg =~ s/([\x22\x5c\n\r\t\f\b])/$esc{$1}/g; + $arg =~ s/\//\\\//g; + $arg =~ s/([\x00-\x08\x0b\x0e-\x1f])/'\\u00' . unpack('H2', $1)/eg; + + utf8::upgrade($arg); + utf8::encode($arg); + + return '"' . $arg . '"'; +} + sub _d { my ($package, undef, $line) = caller 0; @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } @@ -7556,7 +7709,7 @@ my %dsn_for; my $q = new Quoter(); sub main { - @ARGV = @_; # set global ARGV for this package + local @ARGV = @_; # set global ARGV for this package # Reset global vars else tests will have weird results. %dsn_for = (); @@ -7564,7 +7717,8 @@ sub main { # ######################################################################## # Get configuration information. # ######################################################################## - my $o = new OptionParser(); + my $vp = new VersionParser(); + my $o = new OptionParser(); $o->get_specs(); $o->get_opts(); @@ -7674,7 +7828,6 @@ sub main { # Do the work. # ######################################################################## my $tp = new TableParser( Quoter => $q ); - my $vp = new VersionParser(); my $ms = new MasterSlave(VersionParser => $vp); my $du = new MySQLDump( cache => 0 ); my $rt = new Retry(); @@ -7825,6 +7978,13 @@ sub lock_and_rename { tbl => $dsns->[1]->{t}, }; + my $vp = VersionParser->new(); + my %options = ( DSNParser => $dp, OptionParser => $o ); + if ( grep { $vp->version_lt($_->{dbh}, '5.5') } $src, $dst ) { + disconnect($src, $dst); + die "--lock-and-rename requires MySQL 5.5 or later"; + } + if ( $o->get('verbose') ) { print_header("# Lock and rename " . $dp->as_string($src->{dsn})); } @@ -9278,9 +9438,9 @@ In general, this tool is best suited when your tables have a primary key or unique index. Although it can synchronize data in tables lacking a primary key or unique index, it might be best to synchronize that data by another means. -At the time of this release, there is a potential bug using -L<"--lock-and-rename"> with MySQL 5.1, a bug detecting certain differences, -a bug using ROUND() across different platforms, and a bug mixing collations. +At the time of this release, due to bugs in earlier versions of MySQL, +L<"--lock-and-rename"> is disabled in versions earlier than 5.5. Consider +using L<"pt-online-schema-change"> instead. The authoritative source for updated information is always the online issue tracking system. Issues that affect this tool will be marked as such. You can diff --git a/t/pt-table-sync/lock_and_rename.t b/t/pt-table-sync/lock_and_rename.t index ceb29796..fbe15351 100644 --- a/t/pt-table-sync/lock_and_rename.t +++ b/t/pt-table-sync/lock_and_rename.t @@ -22,6 +22,9 @@ my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp); my $master_dbh = $sb->get_dbh_for('master'); my $slave_dbh = $sb->get_dbh_for('slave1'); +if ( $vp->version_le($master_dbh, '5.5') ) { + plan skip_all => "This functionality doesn't work correctly on MySQLs earlier than 5.5"; +} if ( !$master_dbh ) { plan skip_all => 'Cannot connect to sandbox master'; } @@ -59,5 +62,5 @@ like($output, qr/COMMENT='test1'/, '--lock-and-rename worked'); # Done. # ############################################################################# $sb->wipe_clean($master_dbh); -ok($sb->ok(), "Sandbox servers") or BAIL_OUT(__FILE__ . " broke the sandbox"); +is($sb->ok(), '', "Sandbox servers") or BAIL_OUT(__FILE__ . " broke the sandbox"); exit;