diff --git a/bin/pt-table-checksum b/bin/pt-table-checksum index 03d086a3..196a8d10 100755 --- a/bin/pt-table-checksum +++ b/bin/pt-table-checksum @@ -1602,6 +1602,43 @@ sub join_quote { return $db ? "$db.$tbl" : $tbl; } +sub serialize_list { + my ( $self, @args ) = @_; + if ( @args && $args[-1] eq '' ) { + push @args, ''; + } + return join ',', map { quotemeta } @args; +} + +sub deserialize_list { + my ( $self, $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 or absolute end of the string. + /sxg; + + pop @escaped_parts; + + 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; } # ########################################################################### @@ -6753,8 +6790,8 @@ sub exec_nibble { my $dbh = $cxn->dbh(); my $sth = $nibble_iter->statements(); my $boundary = $nibble_iter->boundaries(); - my $lb_quoted = join(',', @{$boundary->{lower}}); - my $ub_quoted = join(',', @{$boundary->{upper}}); + my $lb_quoted = $q->serialize_list(@{$boundary->{lower}}); + my $ub_quoted = $q->serialize_list(@{$boundary->{upper}}); my $chunk = $nibble_iter->nibble_number(); my $chunk_index = $nibble_iter->nibble_index(); diff --git a/bin/pt-table-sync b/bin/pt-table-sync index b423de98..d9307fa6 100755 --- a/bin/pt-table-sync +++ b/bin/pt-table-sync @@ -1104,6 +1104,43 @@ sub join_quote { return $db ? "$db.$tbl" : $tbl; } +sub serialize_list { + my ( $self, @args ) = @_; + if ( @args && $args[-1] eq '' ) { + push @args, ''; + } + return join ',', map { quotemeta } @args; +} + +sub deserialize_list { + my ( $self, $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 or absolute end of the string. + /sxg; + + pop @escaped_parts; + + 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; } # ########################################################################### @@ -8847,16 +8884,14 @@ sub diff_where { MKDEBUG && _d('Ascend params:', Dumper($asc)); } - my @lb = split ',', $diff->{lower_boundary}; my $lb_sql = $asc->{boundaries}->{'>='}; - foreach my $val (@lb) { + foreach my $val ( $q->deserialize_list($diff->{lower_boundary}) ) { my $quoted_val = $q->quote_val($val); $lb_sql =~ s/\?/$val/; } - my @ub = split ',', $diff->{upper_boundary}; my $ub_sql = $asc->{boundaries}->{'<='}; - foreach my $val (@ub) { + foreach my $val ( $q->deserialize_list($diff->{upper_boundary}) ) { my $quoted_val = $q->quote_val($val); $ub_sql =~ s/\?/$val/; } diff --git a/t/lib/Quoter.t b/t/lib/Quoter.t index a876659f..49bab7dd 100644 --- a/t/lib/Quoter.t +++ b/t/lib/Quoter.t @@ -9,7 +9,7 @@ BEGIN { use strict; use warnings FATAL => 'all'; use English qw(-no_match_vars); -use Test::More tests => 47; +use Test::More tests => 52; use Quoter; use PerconaTest; @@ -103,6 +103,24 @@ is( $q->join_quote('`db`', '`foo`.`tbl`'), '`foo`.`tbl`', 'join_merge(`db`, `foo # (de)serialize_list # ########################################################################### +is( + $q->serialize_list(), + undef, + "Serialize empty list" +); + +is( + $q->serialize_list(''), + '', + "Serialize 1 empty string", +); + +is( + $q->serialize_list('', '', ''), + ',,', + "Serialize 3 empty strings", +); + my @serialize_tests = ( [ 'a', 'b', ], [ 'a,', 'b', ], @@ -120,6 +138,8 @@ my @serialize_tests = ( [ 1, 2 ], [ 7, 9 ], [ '', '', '', ], + [ '' ], + [ undef ], ); use DSNParser;