From 1a217a1eec46f06cf9654e8e2f6c6fe0663cac51 Mon Sep 17 00:00:00 2001 From: Brian Fraser Date: Wed, 11 Jul 2012 12:24:29 -0300 Subject: [PATCH 01/27] Add Mo to lib/ --- lib/Mo.pm | 518 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 518 insertions(+) create mode 100644 lib/Mo.pm diff --git a/lib/Mo.pm b/lib/Mo.pm new file mode 100644 index 00000000..dd9fec95 --- /dev/null +++ b/lib/Mo.pm @@ -0,0 +1,518 @@ +# This program is copyright 2007-2011 Baron Schwartz, 2012 Percona Inc. +# Feedback and improvements are welcome. +# +# THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED +# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF +# MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation, version 2; OR the Perl Artistic License. On UNIX and similar +# systems, you can issue `man perlgpl' or `man perlartistic' to read these +# licenses. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., 59 Temple +# Place, Suite 330, Boston, MA 02111-1307 USA. +# ########################################################################### +# Mo package +# ########################################################################### +BEGIN { +# Package: Mo +# Mo provides a miniature object system in the style of Moose and Moo. +package Mo; +our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. + +{ + # Gets the glob from a given string. + no strict 'refs'; + sub _glob_for { + return \*{shift()} + } + + # Gets the stash from a given string. A larger explanation about hashes in Mo::Percona + sub _stash_for { + return \%{ shift() . "::" }; + } +} + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util (); + +# Basic types for isa. If you want a new type, either add it here, +# or give isa a coderef. +our %TYPES = ( + Bool => sub { !$_[0] || (defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == 1) }, + Num => sub { defined $_[0] && &Scalar::Util::looks_like_number }, + Int => sub { defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == int $_[0] }, + Str => sub { defined $_[0] }, + Object => sub { defined $_[0] && &Scalar::Util::blessed }, + FileHandle => sub { local $@; require IO::Handle; fileno($_[0]) && $_[0]->opened }, + + # Ref types: + map { + my $type = /R/ ? $_ : uc $_; + $_ . "Ref" => sub { ref $_[0] eq $type } + } qw(Array Code Hash Regexp Glob Scalar) +); + +our %metadata_for; +{ + # Mo::Object is the parent of every Mo-derived object. Here's where new + # and BUILDARGS gets inherited from. + package Mo::Object; + + sub new { + my $class = shift; + my $args = $class->BUILDARGS(@_); + + my @args_to_delete; + while ( my ($attr, $meta) = each %{$metadata_for{$class}} ) { + next unless exists $meta->{init_arg}; + my $init_arg = $meta->{init_arg}; + + # If init_arg is defined, then we + if ( defined $init_arg ) { + $args->{$attr} = delete $args->{$init_arg}; + } + else { + push @args_to_delete, $attr; + } + } + + delete $args->{$_} for @args_to_delete; + + for my $attribute ( keys %$args ) { + # coerce + if ( my $coerce = $metadata_for{$class}{$attribute}{coerce} ) { + $args->{$attribute} = $coerce->($args->{$attribute}); + } + # isa + if ( my $I = $metadata_for{$class}{$attribute}{isa} ) { + ( (my $I_name), $I ) = @{$I}; + Mo::_check_type_constaints($attribute, $I, $I_name, $args->{$attribute}); + } + } + + while ( my ($attribute, $meta) = each %{$metadata_for{$class}} ) { + next unless $meta->{required}; + Carp::confess("Attribute ($attribute) is required for $class") + if ! exists $args->{$attribute} + } + + @_ = %$args; + my $self = bless $args, $class; + + # BUILD + my @build_subs; + my $linearized_isa = mro::get_linear_isa($class); + + for my $isa_class ( @$linearized_isa ) { + unshift @build_subs, *{ Mo::_glob_for "${isa_class}::BUILD" }{CODE}; + } + # If &class::BUILD exists, for every class in + # the linearized ISA, call it. + # XXX I _think_ that this uses exists correctly, since + # a class could define a stub for BUILD and then AUTOLOAD + # the body. Should check what Moose does. + exists &$_ && $_->( $self, @_ ) for grep { defined } @build_subs; + return $self; + } + + # Base BUILDARGS. + sub BUILDARGS { + shift; + my $ref; + if ( @_ == 1 && ref($_[0]) ) { + Carp::confess("Single parameters to new() must be a HASH ref") + unless ref($_[0]) eq ref({}); + $ref = {%{$_[0]}} # We want a new reference, always + } + else { + $ref = { @_ }; + } + return $ref; + } +} + +my %export_for; +sub Mo::import { + # Set warnings and strict for the caller. + warnings->import(qw(FATAL all)); + strict->import(); + + my $caller = scalar caller(); # Caller's package + my $caller_pkg = $caller . "::"; # Caller's package with :: at the end + my (%exports, %options); + + # Load each feature and call its &e. + my (undef, @features) = @_; + my %ignore = ( map { $_ => 1 } qw( is isa init_arg builder buildargs clearer predicate build handles default required ) ); + for my $feature (grep { !$ignore{$_} } @features) { + { local $@; require "Mo/$feature.pm"; } + { + no strict 'refs'; + &{"Mo::${feature}::e"}( + $caller_pkg, + \%exports, + \%options, + \@_ + ); + } + } + + return if $exports{M}; + + %exports = ( + extends => sub { + for my $class ( map { "$_" } @_ ) { + # Try loading the class, but don't croak if we fail. + $class =~ s{::|'}{/}g; + { local $@; eval { require "$class.pm" } } # or warn $@; + } + _set_package_isa($caller, @_); + _set_inherited_metadata($caller); + }, + has => sub { + my $names = shift; + for my $attribute ( ref $names ? @$names : $names ) { + my %args = @_; + my $method = ($args{is} || '') eq 'ro' + ? sub { + Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller_pkg}${attribute}") + if $#_; + return $_[0]{$attribute}; + } + : sub { + return $#_ + ? $_[0]{$attribute} = $_[1] + : $_[0]{$attribute}; + }; + + $metadata_for{$caller}{$attribute} = (); + + # isa => Constaint, + if ( my $I = $args{isa} ) { + my $orig_I = $I; + my $type; + if ( $I =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $I = _nested_constraints($attribute, $1, $2); + } + $metadata_for{$caller}{$attribute}{isa} = [$orig_I, $I]; + my $orig_method = $method; + $method = sub { + if ( $#_ ) { + Mo::_check_type_constaints($attribute, $I, $orig_I, $_[1]); + } + goto &$orig_method; + }; + } + + # XXX TODO: Inline builder and default into the actual method, for speed. + # builder => '_builder_method', + if ( my $builder = $args{builder} ) { + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$builder + : goto &$original_method + }; + } + + # default => CodeRef, + if ( my $code = $args{default} ) { + Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") + unless ref($code) eq 'CODE'; + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$code + : goto &$original_method + }; + } + + # does => 'Role', + if ( my $role = $args{does} ) { + my $original_method = $method; + $method = sub { + if ( $#_ ) { + Carp::confess(qq) + unless blessed($_[1]) && $_[1]->does($role) + } + goto &$original_method + }; + } + + # coerce => CodeRef, + if ( my $coercion = $args{coerce} ) { + $metadata_for{$caller}{$attribute}{coerce} = $coercion; + my $original_method = $method; + $method = sub { + if ( $#_ ) { + return $original_method->($_[0], $coercion->($_[1])) + } + goto &$original_method; + } + } + + # Call the extra features; that is, things loaded from + # the Mo::etc namespace, and not implemented here. + $method = $options{$_}->($method, $attribute, @_) + for sort keys %options; + + # Actually put the attribute's accessor in the class + *{ _glob_for "${caller}::$attribute" } = $method; + + if ( $args{required} ) { + $metadata_for{$caller}{$attribute}{required} = 1; + } + + if ($args{clearer}) { + *{ _glob_for "${caller}::$args{clearer}" } + = sub { delete shift->{$attribute} } + } + + if ($args{predicate}) { + *{ _glob_for "${caller}::$args{predicate}" } + = sub { exists shift->{$attribute} } + } + + if ($args{handles}) { + _has_handles($caller, $attribute, \%args); + } + + if (exists $args{init_arg}) { + $metadata_for{$caller}{$attribute}{init_arg} = $args{init_arg}; + } + } + }, + %exports, + ); + + # We keep this so code doing 'no Mo;' actually does a cleanup. + $export_for{$caller} = [ keys %exports ]; + + # Export has, extends and sosuch. + for my $keyword ( keys %exports ) { + *{ _glob_for "${caller}::$keyword" } = $exports{$keyword} + } + # Set up our caller's ISA, unless they already set it manually themselves, + # in which case we assume they know what they are doing. + # XXX weird syntax here because we want to call the classes' extends at + # least once, to avoid warnings. + *{ _glob_for "${caller}::extends" }{CODE}->( "Mo::Object" ) + unless @{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] }; +}; + +sub _check_type_constaints { + my ($attribute, $I, $I_name, $val) = @_; + ( ref($I) eq 'CODE' + ? $I->($val) + : (ref $val eq $I + || ($val && $val eq $I) + || (exists $TYPES{$I} && $TYPES{$I}->($val))) + ) + || Carp::confess( + qq + . qq + . (defined $val ? Mo::Dumper($val) : 'undef') ) +} + +# handles handles +sub _has_handles { + my ($caller, $attribute, $args) = @_; + my $handles = $args->{handles}; + + my $ref = ref $handles; + my $kv; + if ( $ref eq ref [] ) { + # handles => [ ... list of methods ... ], + $kv = { map { $_,$_ } @{$handles} }; + } + elsif ( $ref eq ref {} ) { + # handles => { 'method_to_install' => 'original_method' | [ 'original_method', ... curried arguments ... ], }, + $kv = $handles; + } + elsif ( $ref eq ref qr// ) { + # handles => qr/PAT/, + Carp::confess("Cannot delegate methods based on a Regexp without a type constraint (isa)") + unless $args->{isa}; + my $target_class = $args->{isa}; + $kv = { + map { $_, $_ } + grep { $_ =~ $handles } + grep { !exists $Mo::Object::{$_} && $target_class->can($_) } + grep { $_ ne 'has' && $_ ne 'extends' } + keys %{ _stash_for $target_class } + }; + } + else { + Carp::confess("handles for $ref not yet implemented"); + } + + while ( my ($method, $target) = each %{$kv} ) { + my $name = _glob_for "${caller}::$method"; + Carp::confess("You cannot overwrite a locally defined method ($method) with a delegation") + if defined &$name; + + # If we have an arrayref, they are currying some arguments. + my ($target, @curried_args) = ref($target) ? @$target : $target; + *$name = sub { + my $self = shift; + my $delegate_to = $self->$attribute(); + my $error = "Cannot delegate $method to $target because the value of $attribute"; + Carp::confess("$error is not defined") unless $delegate_to; + Carp::confess("$error is not an object (got '$delegate_to')") + unless Scalar::Util::blessed($delegate_to) || (!ref($delegate_to) && $delegate_to->can($target)); + return $delegate_to->$target(@curried_args, @_); + } + } +} + +# Nested (or parametized) constraints look like this: ArrayRef[CONSTRAINT] or +# Maybe[CONSTRAINT]. This function returns a coderef that implements one of +# these. +sub _nested_constraints { + my ($attribute, $aggregate_type, $type) = @_; + + my $inner_types; + if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + # If the inner constraint -- the part within brackets -- is also a parametized + # constraint, then call this function recursively. + $inner_types = _nested_constraints($1, $2); + } + else { + # Otherwise, try checking if it's one of the built-in types. + $inner_types = $TYPES{$type}; + } + + if ( $aggregate_type eq 'ArrayRef' ) { + return sub { + my ($val) = @_; + return unless ref($val) eq ref([]); + + if ($inner_types) { + for my $value ( @{$val} ) { + return unless $inner_types->($value) + } + } + else { + # $inner_types isn't set, we are dealing with a class name. + for my $value ( @{$val} ) { + return unless $value && ($value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type))); + } + } + return 1; + }; + } + elsif ( $aggregate_type eq 'Maybe' ) { + return sub { + my ($value) = @_; + # For Maybe, undef is valid + return 1 if ! defined($value); + # Otherwise, defer to the inner type + if ($inner_types) { + return unless $inner_types->($value) + } + else { + return unless $value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type)); + } + return 1; + } + } + else { + Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); + } +} + +# Sets a package's @ISA to the list passed in. Overwrites any previous values. +sub _set_package_isa { + my ($package, @new_isa) = @_; + + *{ _glob_for "${package}::ISA" } = [@new_isa]; +} + +# Each class has its own metadata. When a class inhyerits attributes, +# it should also inherit the attribute metadata. +sub _set_inherited_metadata { + my $class = shift; + my $linearized_isa = mro::get_linear_isa($class); + my %new_metadata; + + # Walk @ISA in reverse, grabbing the metadata for each + # class. Attributes with the same name defined in more + # specific classes override their parent's attributes. + for my $isa_class (reverse @$linearized_isa) { + %new_metadata = ( + %new_metadata, + %{ $metadata_for{$isa_class} || {} }, + ); + } + $metadata_for{$class} = \%new_metadata; +} + +sub unimport { + my $caller = scalar caller(); + my $stash = _stash_for( $caller ); + + delete $stash->{$_} for @{$export_for{$caller}}; +} + +sub Dumper { + require Data::Dumper; + local $Data::Dumper::Indent = 0; + local $Data::Dumper::Sortkeys = 0; + local $Data::Dumper::Quotekeys = 0; + local $Data::Dumper::Terse = 1; + + Data::Dumper::Dumper(@_) +} + +BEGIN { + # mro is the method resolution order. The module itself is core in + # recent Perls; In older Perls it's available from MRO::Compat from + # CPAN, and in case that isn't available to us, we inline the barest + # funcionality. + if ($] >= 5.010) { + { local $@; require mro; } + } + else { + local $@; + eval { + require MRO::Compat; + } or do { + *mro::get_linear_isa = *mro::get_linear_isa_dfs = sub { + no strict 'refs'; + + my $classname = shift; + + my @lin = ($classname); + my %stored; + foreach my $parent (@{"$classname\::ISA"}) { + my $plin = mro::get_linear_isa_dfs($parent); + foreach (@$plin) { + next if exists $stored{$_}; + push(@lin, $_); + $stored{$_} = 1; + } + } + return \@lin; + }; + } + } +} + +} +1; +# ########################################################################### +# End Mo package +# ########################################################################### From c0121599896ff8767236339f1b5bc43b89396cd9 Mon Sep 17 00:00:00 2001 From: Brian Fraser Date: Wed, 11 Jul 2012 12:25:54 -0300 Subject: [PATCH 02/27] Make Mo add itself to %INC --- lib/Mo.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Mo.pm b/lib/Mo.pm index dd9fec95..05159733 100644 --- a/lib/Mo.pm +++ b/lib/Mo.pm @@ -20,6 +20,7 @@ BEGIN { # Package: Mo # Mo provides a miniature object system in the style of Moose and Moo. +$INC{"Mo.pm"} = __FILE__; package Mo; our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. From bcc618ba5bfe838b04de002c56447f9247ac687b Mon Sep 17 00:00:00 2001 From: Brian Fraser Date: Wed, 11 Jul 2012 15:04:50 -0300 Subject: [PATCH 03/27] Fixup: A mistakenly moved BEGIN { was breaking things --- lib/Mo.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Mo.pm b/lib/Mo.pm index 05159733..cbc5764e 100644 --- a/lib/Mo.pm +++ b/lib/Mo.pm @@ -17,9 +17,9 @@ # ########################################################################### # Mo package # ########################################################################### -BEGIN { # Package: Mo # Mo provides a miniature object system in the style of Moose and Moo. +BEGIN { $INC{"Mo.pm"} = __FILE__; package Mo; our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. From acd5281e3c7ccdd07ab21a3d2b40e1cbd56bbc69 Mon Sep 17 00:00:00 2001 From: Brian Fraser Date: Wed, 11 Jul 2012 15:05:00 -0300 Subject: [PATCH 04/27] Redesigned VersionParser. Now using Mo and overloading, so that $version_object < 5.1 is the new way to test things. --- lib/VersionParser.pm | 204 ++++++++++++++++++++++++++----------------- 1 file changed, 126 insertions(+), 78 deletions(-) diff --git a/lib/VersionParser.pm b/lib/VersionParser.pm index 973161a8..a10b04f6 100644 --- a/lib/VersionParser.pm +++ b/lib/VersionParser.pm @@ -22,103 +22,150 @@ # VersionParser parses a MySQL version string. package VersionParser; -use strict; -use warnings FATAL => 'all'; +use Mo; +use Scalar::Util qw(blessed); use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; -sub new { - my ( $class ) = @_; - bless {}, $class; -} +use overload ( + '""' => "version", + # All the other operators are defined through these + '<=>' => "cmp", + 'cmp' => "cmp", + fallback => 1, +); -sub parse { - my ( $self, $str ) = @_; - my @version_parts = $str =~ m/(\d+)/g; - # Turn a version like 5.5 into 5.5.0 - @version_parts = map { $_ || 0 } @version_parts[0..2]; - my $result = sprintf('%03d%03d%03d', @version_parts); - PTDEBUG && _d($str, 'parses to', $result); - return $result; -} +our $VERSION = 0.01; -# Compares versions like 5.0.27 and 4.1.15-standard-log. Caches version number -# for each DBH for later use. -sub version_cmp { - my ($self, $dbh, $target, $cmp) = @_; - my $version = $self->version($dbh); - my $result; +has major => ( + is => 'ro', + isa => 'Int', + required => 1, +); - 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" - } +has [qw( minor revision )] => ( + is => 'ro', + isa => 'Num', +); - PTDEBUG && _d($self->{$dbh}, $cmp, $target, ':', $result); - return $result; -} +has flavor => ( + is => 'ro', + isa => 'Str', + default => sub { 'Unknown' }, +); -sub version_ge { - my ( $self, $dbh, $target ) = @_; - return $self->version_cmp($dbh, $target, 'ge'); -} +has innodb_version => ( + is => 'ro', + isa => 'Str', + default => sub { 'NO' }, +); -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 series { + my $self = shift; + return $self->_join_version($self->major, $self->minor); } sub version { - my ( $self, $dbh ) = @_; - if ( !$self->{$dbh} ) { - $self->{$dbh} = $self->parse( - $dbh->selectrow_array('SELECT VERSION()')); + my $self = shift; + return $self->_join_version($self->major, $self->minor, $self->revision); +} + +sub is_in { + my ($self, $target) = @_; + + return $self eq $target; +} + +# Internal +sub _join_version { + my ($self, @parts) = @_; + + return join ".", map { my $c = $_; $c =~ s/^0\./0/; $c } grep defined, @parts; +} +# Internal +sub _split_version { + my ($self, $str) = @_; + my @version_parts = map { s/^0(?=\d)/0./; $_ } $str =~ m/(\d+)/g; + # Turn a version like 5.5 into 5.5.0 + return @version_parts[0..2]; +} + +# Returns the version formatted as %d%02d%02d; that is, 5.1.20 would become +# 50120 +sub normalized_version { + my ( $self ) = @_; + my @version_parts = map { $_ || 0 } $self->_split_version( $self->version ); + my $result = sprintf('%d%02d%02d', @version_parts); + PTDEBUG && _d($self->version, 'normalizes to', $result); + return $result; +} + +# Returns a comment in the form of /*!$self->normalized_version $cmd */ +sub comment { + my ( $self, $cmd ) = @_; + my $v = $self->normalized_version(); + + return "/*!$v $cmd */" +} + +my @methods = qw(major minor revision); +sub cmp { + my ($left, $right) = @_; + my $right_obj = (blessed($right) && $right->isa(ref($left))) + ? $right + : ref($left)->new($right); + + my $retval = 0; + for my $m ( @methods ) { + last unless defined($left->$m) && defined($right_obj->$m); + $retval = $left->$m <=> $right_obj->$m; + last if $retval; } - return $self->{$dbh}; + return $retval; +} + +sub BUILDARGS { + my $self = shift; + + if ( @_ == 1 ) { + my %args; + if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { + my $dbh = $_[0]; + my $query; + PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); + if ( eval { $query = $dbh->selectall_hashref(q) } ) { + @args{@methods} = $self->_split_version($query->{version}); + $args{flavor} = delete $query->{version_comment} + if $query->{version_comment}; + } + elsif ( eval { ($query) = $dbh->selectrow_array('SELECT VERSION()') } ) { + @args{@methods} = $self->_split_version($query); + } + else { + PTDEBUG && _d("Couldn't get the version from the dbh: $@"); + # No need to die here; ->new will die on it's own since the version + # is missing + } + $args{innodb_version} = eval { $self->_innodb_version($dbh) }; + } + elsif ( !ref($_[0]) ) { + @args{@methods} = $self->_split_version($_[0]); + } + + for my $method (@methods) { + delete $args{$method} unless defined $args{$method}; + } + @_ = %args if %args; + } + + return $self->SUPER::BUILDARGS(@_); } # Returns DISABLED if InnoDB doesn't appear as YES or DEFAULT in SHOW ENGINES, # BUILTIN if there is no innodb_version variable in SHOW VARIABLES, or # if there is an innodb_version variable in SHOW VARIABLES, or # NO if SHOW ENGINES is broken or InnDB doesn't appear in it. -sub innodb_version { +sub _innodb_version { my ( $self, $dbh ) = @_; return unless $dbh; my $innodb_version = "NO"; @@ -156,6 +203,7 @@ sub _d { print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; } +no Mo; 1; } # ########################################################################### From f30c50be446bb9232a738edb07e14f9d70de6711 Mon Sep 17 00:00:00 2001 From: Brian Fraser Date: Wed, 11 Jul 2012 15:10:33 -0300 Subject: [PATCH 05/27] Updated the fles in /lib to use the new VersionParser --- lib/MasterSlave.pm | 3 +-- lib/TableChecksum.pm | 9 +++++---- lib/TableParser.pm | 5 ++--- lib/TableSyncer.pm | 8 +++----- lib/VariableAdvisorRules.pm | 28 ++++++---------------------- 5 files changed, 17 insertions(+), 36 deletions(-) diff --git a/lib/MasterSlave.pm b/lib/MasterSlave.pm index 91e61769..ef74a01a 100644 --- a/lib/MasterSlave.pm +++ b/lib/MasterSlave.pm @@ -279,8 +279,7 @@ sub get_connected_slaves { # user with USER(), quote it, and then add it to statement. my $show = "SHOW GRANTS FOR "; my $user = 'CURRENT_USER()'; - my $vp = $self->{VersionParser}; - if ( $vp && !$vp->version_ge($dbh, '4.1.2') ) { + if ( VersionParser->new($dbh) < '4.1.2' ) { $user = $dbh->selectrow_arrayref('SELECT USER()')->[0]; $user =~ s/([^@]+)@(.+)/'$1'\@'$2'/; } diff --git a/lib/TableChecksum.pm b/lib/TableChecksum.pm index f8a35ae9..714e7de0 100644 --- a/lib/TableChecksum.pm +++ b/lib/TableChecksum.pm @@ -39,7 +39,7 @@ our %ALGOS = ( sub new { my ( $class, %args ) = @_; - foreach my $arg ( qw(Quoter VersionParser) ) { + foreach my $arg ( qw(Quoter) ) { die "I need a $arg argument" unless defined $args{$arg}; } my $self = { %args }; @@ -107,23 +107,24 @@ sub get_crc_type { sub best_algorithm { my ( $self, %args ) = @_; my ( $alg, $dbh ) = @args{ qw(algorithm dbh) }; - my $vp = $self->{VersionParser}; my @choices = sort { $ALGOS{$a}->{pref} <=> $ALGOS{$b}->{pref} } keys %ALGOS; die "Invalid checksum algorithm $alg" if $alg && !$ALGOS{$alg}; + my $version = VersionParser->new($dbh); + # CHECKSUM is eliminated by lots of things... if ( $args{where} || $args{chunk} # CHECKSUM does whole table || $args{replicate} # CHECKSUM can't do INSERT.. SELECT - || !$vp->version_ge($dbh, '4.1.1')) # CHECKSUM doesn't exist + || $version < '4.1.1') # CHECKSUM doesn't exist { PTDEBUG && _d('Cannot use CHECKSUM algorithm'); @choices = grep { $_ ne 'CHECKSUM' } @choices; } # BIT_XOR isn't available till 4.1.1 either - if ( !$vp->version_ge($dbh, '4.1.1') ) { + if ( $version < '4.1.1' ) { PTDEBUG && _d('Cannot use BIT_XOR algorithm because MySQL < 4.1.1'); @choices = grep { $_ ne 'BIT_XOR' } @choices; } diff --git a/lib/TableParser.pm b/lib/TableParser.pm index f9cbbfc1..1e711d48 100644 --- a/lib/TableParser.pm +++ b/lib/TableParser.pm @@ -27,8 +27,7 @@ # # And some subs have an optional $opts param which is a hashref of options. # $opts->{mysql_version} is typically used, which is the return value from -# VersionParser::parser() (which returns a zero-padded MySQL version, -# e.g. 004001000 for 4.1.0). +# VersionParser->new() package TableParser; use strict; @@ -421,7 +420,7 @@ sub get_keys { my ( $type, $cols ) = $key =~ m/(?:USING (\w+))? \((.+)\)/; my ( $special ) = $key =~ m/(FULLTEXT|SPATIAL)/; $type = $type || $special || 'BTREE'; - if ( $opts->{mysql_version} && $opts->{mysql_version} lt '004001000' + if ( $opts->{mysql_version} && $opts->{mysql_version} lt '4.1' && $engine =~ m/HEAP|MEMORY/i ) { $type = 'HASH'; # MySQL pre-4.1 supports only HASH indexes on HEAP diff --git a/lib/TableSyncer.pm b/lib/TableSyncer.pm index b02a8854..24f48083 100644 --- a/lib/TableSyncer.pm +++ b/lib/TableSyncer.pm @@ -35,13 +35,12 @@ $Data::Dumper::Quotekeys = 0; # Arguments: # * MasterSlave A MasterSlave module # * Quoter A Quoter module -# * VersionParser A VersionParser module # * TableChecksum A TableChecksum module # * Retry A Retry module # * DSNParser (optional) sub new { my ( $class, %args ) = @_; - my @required_args = qw(MasterSlave Quoter VersionParser TableChecksum Retry); + my @required_args = qw(MasterSlave Quoter TableChecksum Retry); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless defined $args{$arg}; } @@ -127,7 +126,6 @@ sub sync_table { $args{timeout_ok} ||= 0; my $q = $self->{Quoter}; - my $vp = $self->{VersionParser}; # ######################################################################## # Get and prepare the first plugin that can sync this table. @@ -145,8 +143,8 @@ sub sync_table { # Make an index hint for either the explicitly given chunk_index # or the chunk_index chosen by the plugin if index_hint is true. my $index_hint; - my $hint = ($vp->version_ge($src->{dbh}, '4.0.9') - && $vp->version_ge($dst->{dbh}, '4.0.9') ? 'FORCE' : 'USE') + my $hint = ((VersionParser->new($src->{dbh}) >= '4.0.9' + && VersionParser->new($dst->{dbh}) >= '4.0.9') ? 'FORCE' : 'USE') . ' INDEX'; if ( $args{chunk_index} ) { PTDEBUG && _d('Using given chunk index for index hint'); diff --git a/lib/VariableAdvisorRules.pm b/lib/VariableAdvisorRules.pm index 86ac033a..3905355e 100644 --- a/lib/VariableAdvisorRules.pm +++ b/lib/VariableAdvisorRules.pm @@ -539,27 +539,11 @@ sub get_rules { my ( %args ) = @_; my $mysql_version = $args{mysql_version}; return 0 unless $mysql_version; - my ($major, $minor, $patch) = $mysql_version =~ m/(\d{3})/g; - if ( $major eq '003' ) { - return $mysql_version lt '003023000' ? 1 : 0; # 3.23.x - } - elsif ( $major eq '004' ) { - return $mysql_version lt '004001020' ? 1 : 0; # 4.1.20 - } - elsif ( $major eq '005' ) { - if ( $minor eq '000' ) { - return $mysql_version lt '005000037' ? 1 : 0; # 5.0.37 - } - elsif ( $minor eq '001' ) { - return $mysql_version lt '005001030' ? 1 : 0; # 5.1.30 - } - else { - return 0; - } - } - else { - return 0; - } + return 1 if ($mysql_version eq '3' && $mysql_version lt '3.23') + || ($mysql_version eq '4' && $mysql_version lt '4.1.20') + || ($mysql_version eq '5.0' && $mysql_version lt '5.0.37') + || ($mysql_version eq '5.1' && $mysql_version lt '5.1.30'); + return 0; }, }, { @@ -568,7 +552,7 @@ sub get_rules { my ( %args ) = @_; my $mysql_version = $args{mysql_version}; return 0 unless $mysql_version; - return $mysql_version lt '005001000' ? 1 : 0; # 5.1.x + return $mysql_version lt '5.1' ? 1 : 0; # 5.1.x }, }, }; From f9a29fed378e1799779367ff8e0eb08706e8bfab Mon Sep 17 00:00:00 2001 From: Brian Fraser Date: Wed, 11 Jul 2012 15:12:52 -0300 Subject: [PATCH 06/27] Updated all the tools to use the new VersionParser --- bin/pt-archiver | 656 ++++++++++++++++++++++++++++----- bin/pt-deadlock-logger | 646 ++++++++++++++++++++++++++++---- bin/pt-duplicate-key-checker | 651 ++++++++++++++++++++++++++++---- bin/pt-find | 649 ++++++++++++++++++++++++++++---- bin/pt-heartbeat | 655 ++++++++++++++++++++++++++++----- bin/pt-index-usage | 652 ++++++++++++++++++++++++++++---- bin/pt-kill | 538 +++++++++++++++++++++------ bin/pt-online-schema-change | 652 ++++++++++++++++++++++++++++---- bin/pt-slave-delay | 535 +++++++++++++++++++++------ bin/pt-slave-find | 657 ++++++++++++++++++++++++++++----- bin/pt-slave-restart | 652 ++++++++++++++++++++++++++++---- bin/pt-table-checksum | 654 +++++++++++++++++++++++++++----- bin/pt-table-sync | 695 +++++++++++++++++++++++++++++------ bin/pt-upgrade | 669 ++++++++++++++++++++++++++++----- bin/pt-variable-advisor | 679 +++++++++++++++++++++++++++++----- 15 files changed, 8297 insertions(+), 1343 deletions(-) diff --git a/bin/pt-archiver b/bin/pt-archiver index d26e4c2f..548bcd39 100755 --- a/bin/pt-archiver +++ b/bin/pt-archiver @@ -1033,6 +1033,456 @@ if ( PTDEBUG ) { # End OptionParser package # ########################################################################### +# ########################################################################### +# Mo 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/Mo.pm +# t/lib/Mo.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +BEGIN { +$INC{"Mo.pm"} = __FILE__; +package Mo; +our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. + +{ + no strict 'refs'; + sub _glob_for { + return \*{shift()} + } + + sub _stash_for { + return \%{ shift() . "::" }; + } +} + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util (); + +our %TYPES = ( + Bool => sub { !$_[0] || (defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == 1) }, + Num => sub { defined $_[0] && &Scalar::Util::looks_like_number }, + Int => sub { defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == int $_[0] }, + Str => sub { defined $_[0] }, + Object => sub { defined $_[0] && &Scalar::Util::blessed }, + FileHandle => sub { local $@; require IO::Handle; fileno($_[0]) && $_[0]->opened }, + + map { + my $type = /R/ ? $_ : uc $_; + $_ . "Ref" => sub { ref $_[0] eq $type } + } qw(Array Code Hash Regexp Glob Scalar) +); + +our %metadata_for; +{ + package Mo::Object; + + sub new { + my $class = shift; + my $args = $class->BUILDARGS(@_); + + my @args_to_delete; + while ( my ($attr, $meta) = each %{$metadata_for{$class}} ) { + next unless exists $meta->{init_arg}; + my $init_arg = $meta->{init_arg}; + + if ( defined $init_arg ) { + $args->{$attr} = delete $args->{$init_arg}; + } + else { + push @args_to_delete, $attr; + } + } + + delete $args->{$_} for @args_to_delete; + + for my $attribute ( keys %$args ) { + if ( my $coerce = $metadata_for{$class}{$attribute}{coerce} ) { + $args->{$attribute} = $coerce->($args->{$attribute}); + } + if ( my $I = $metadata_for{$class}{$attribute}{isa} ) { + ( (my $I_name), $I ) = @{$I}; + Mo::_check_type_constaints($attribute, $I, $I_name, $args->{$attribute}); + } + } + + while ( my ($attribute, $meta) = each %{$metadata_for{$class}} ) { + next unless $meta->{required}; + Carp::confess("Attribute ($attribute) is required for $class") + if ! exists $args->{$attribute} + } + + @_ = %$args; + my $self = bless $args, $class; + + my @build_subs; + my $linearized_isa = mro::get_linear_isa($class); + + for my $isa_class ( @$linearized_isa ) { + unshift @build_subs, *{ Mo::_glob_for "${isa_class}::BUILD" }{CODE}; + } + exists &$_ && $_->( $self, @_ ) for grep { defined } @build_subs; + return $self; + } + + sub BUILDARGS { + shift; + my $ref; + if ( @_ == 1 && ref($_[0]) ) { + Carp::confess("Single parameters to new() must be a HASH ref") + unless ref($_[0]) eq ref({}); + $ref = {%{$_[0]}} # We want a new reference, always + } + else { + $ref = { @_ }; + } + return $ref; + } +} + +my %export_for; +sub Mo::import { + warnings->import(qw(FATAL all)); + strict->import(); + + my $caller = scalar caller(); # Caller's package + my $caller_pkg = $caller . "::"; # Caller's package with :: at the end + my (%exports, %options); + + my (undef, @features) = @_; + my %ignore = ( map { $_ => 1 } qw( is isa init_arg builder buildargs clearer predicate build handles default required ) ); + for my $feature (grep { !$ignore{$_} } @features) { + { local $@; require "Mo/$feature.pm"; } + { + no strict 'refs'; + &{"Mo::${feature}::e"}( + $caller_pkg, + \%exports, + \%options, + \@_ + ); + } + } + + return if $exports{M}; + + %exports = ( + extends => sub { + for my $class ( map { "$_" } @_ ) { + $class =~ s{::|'}{/}g; + { local $@; eval { require "$class.pm" } } # or warn $@; + } + _set_package_isa($caller, @_); + _set_inherited_metadata($caller); + }, + has => sub { + my $names = shift; + for my $attribute ( ref $names ? @$names : $names ) { + my %args = @_; + my $method = ($args{is} || '') eq 'ro' + ? sub { + Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller_pkg}${attribute}") + if $#_; + return $_[0]{$attribute}; + } + : sub { + return $#_ + ? $_[0]{$attribute} = $_[1] + : $_[0]{$attribute}; + }; + + $metadata_for{$caller}{$attribute} = (); + + if ( my $I = $args{isa} ) { + my $orig_I = $I; + my $type; + if ( $I =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $I = _nested_constraints($attribute, $1, $2); + } + $metadata_for{$caller}{$attribute}{isa} = [$orig_I, $I]; + my $orig_method = $method; + $method = sub { + if ( $#_ ) { + Mo::_check_type_constaints($attribute, $I, $orig_I, $_[1]); + } + goto &$orig_method; + }; + } + + if ( my $builder = $args{builder} ) { + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$builder + : goto &$original_method + }; + } + + if ( my $code = $args{default} ) { + Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") + unless ref($code) eq 'CODE'; + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$code + : goto &$original_method + }; + } + + if ( my $role = $args{does} ) { + my $original_method = $method; + $method = sub { + if ( $#_ ) { + Carp::confess(qq) + unless blessed($_[1]) && $_[1]->does($role) + } + goto &$original_method + }; + } + + if ( my $coercion = $args{coerce} ) { + $metadata_for{$caller}{$attribute}{coerce} = $coercion; + my $original_method = $method; + $method = sub { + if ( $#_ ) { + return $original_method->($_[0], $coercion->($_[1])) + } + goto &$original_method; + } + } + + $method = $options{$_}->($method, $attribute, @_) + for sort keys %options; + + *{ _glob_for "${caller}::$attribute" } = $method; + + if ( $args{required} ) { + $metadata_for{$caller}{$attribute}{required} = 1; + } + + if ($args{clearer}) { + *{ _glob_for "${caller}::$args{clearer}" } + = sub { delete shift->{$attribute} } + } + + if ($args{predicate}) { + *{ _glob_for "${caller}::$args{predicate}" } + = sub { exists shift->{$attribute} } + } + + if ($args{handles}) { + _has_handles($caller, $attribute, \%args); + } + + if (exists $args{init_arg}) { + $metadata_for{$caller}{$attribute}{init_arg} = $args{init_arg}; + } + } + }, + %exports, + ); + + $export_for{$caller} = [ keys %exports ]; + + for my $keyword ( keys %exports ) { + *{ _glob_for "${caller}::$keyword" } = $exports{$keyword} + } + *{ _glob_for "${caller}::extends" }{CODE}->( "Mo::Object" ) + unless @{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] }; +}; + +sub _check_type_constaints { + my ($attribute, $I, $I_name, $val) = @_; + ( ref($I) eq 'CODE' + ? $I->($val) + : (ref $val eq $I + || ($val && $val eq $I) + || (exists $TYPES{$I} && $TYPES{$I}->($val))) + ) + || Carp::confess( + qq + . qq + . (defined $val ? Mo::Dumper($val) : 'undef') ) +} + +sub _has_handles { + my ($caller, $attribute, $args) = @_; + my $handles = $args->{handles}; + + my $ref = ref $handles; + my $kv; + if ( $ref eq ref [] ) { + $kv = { map { $_,$_ } @{$handles} }; + } + elsif ( $ref eq ref {} ) { + $kv = $handles; + } + elsif ( $ref eq ref qr// ) { + Carp::confess("Cannot delegate methods based on a Regexp without a type constraint (isa)") + unless $args->{isa}; + my $target_class = $args->{isa}; + $kv = { + map { $_, $_ } + grep { $_ =~ $handles } + grep { !exists $Mo::Object::{$_} && $target_class->can($_) } + grep { $_ ne 'has' && $_ ne 'extends' } + keys %{ _stash_for $target_class } + }; + } + else { + Carp::confess("handles for $ref not yet implemented"); + } + + while ( my ($method, $target) = each %{$kv} ) { + my $name = _glob_for "${caller}::$method"; + Carp::confess("You cannot overwrite a locally defined method ($method) with a delegation") + if defined &$name; + + my ($target, @curried_args) = ref($target) ? @$target : $target; + *$name = sub { + my $self = shift; + my $delegate_to = $self->$attribute(); + my $error = "Cannot delegate $method to $target because the value of $attribute"; + Carp::confess("$error is not defined") unless $delegate_to; + Carp::confess("$error is not an object (got '$delegate_to')") + unless Scalar::Util::blessed($delegate_to) || (!ref($delegate_to) && $delegate_to->can($target)); + return $delegate_to->$target(@curried_args, @_); + } + } +} + +sub _nested_constraints { + my ($attribute, $aggregate_type, $type) = @_; + + my $inner_types; + if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $inner_types = _nested_constraints($1, $2); + } + else { + $inner_types = $TYPES{$type}; + } + + if ( $aggregate_type eq 'ArrayRef' ) { + return sub { + my ($val) = @_; + return unless ref($val) eq ref([]); + + if ($inner_types) { + for my $value ( @{$val} ) { + return unless $inner_types->($value) + } + } + else { + for my $value ( @{$val} ) { + return unless $value && ($value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type))); + } + } + return 1; + }; + } + elsif ( $aggregate_type eq 'Maybe' ) { + return sub { + my ($value) = @_; + return 1 if ! defined($value); + if ($inner_types) { + return unless $inner_types->($value) + } + else { + return unless $value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type)); + } + return 1; + } + } + else { + Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); + } +} + +sub _set_package_isa { + my ($package, @new_isa) = @_; + + *{ _glob_for "${package}::ISA" } = [@new_isa]; +} + +sub _set_inherited_metadata { + my $class = shift; + my $linearized_isa = mro::get_linear_isa($class); + my %new_metadata; + + for my $isa_class (reverse @$linearized_isa) { + %new_metadata = ( + %new_metadata, + %{ $metadata_for{$isa_class} || {} }, + ); + } + $metadata_for{$class} = \%new_metadata; +} + +sub unimport { + my $caller = scalar caller(); + my $stash = _stash_for( $caller ); + + delete $stash->{$_} for @{$export_for{$caller}}; +} + +sub Dumper { + require Data::Dumper; + local $Data::Dumper::Indent = 0; + local $Data::Dumper::Sortkeys = 0; + local $Data::Dumper::Quotekeys = 0; + local $Data::Dumper::Terse = 1; + + Data::Dumper::Dumper(@_) +} + +BEGIN { + if ($] >= 5.010) { + { local $@; require mro; } + } + else { + local $@; + eval { + require MRO::Compat; + } or do { + *mro::get_linear_isa = *mro::get_linear_isa_dfs = sub { + no strict 'refs'; + + my $classname = shift; + + my @lin = ($classname); + my %stored; + foreach my $parent (@{"$classname\::ISA"}) { + my $plin = mro::get_linear_isa_dfs($parent); + foreach (@$plin) { + next if exists $stored{$_}; + push(@lin, $_); + $stored{$_} = 1; + } + } + return \@lin; + }; + } + } +} + +} +1; +} +# ########################################################################### +# End Mo package +# ########################################################################### + # ########################################################################### # TableParser package # This package is a copy without comments from the original. The original @@ -1337,7 +1787,7 @@ sub get_keys { my ( $type, $cols ) = $key =~ m/(?:USING (\w+))? \((.+)\)/; my ( $special ) = $key =~ m/(FULLTEXT|SPATIAL)/; $type = $type || $special || 'BTREE'; - if ( $opts->{mysql_version} && $opts->{mysql_version} lt '004001000' + if ( $opts->{mysql_version} && $opts->{mysql_version} lt '4.1' && $engine =~ m/HEAP|MEMORY/i ) { $type = 'HASH'; # MySQL pre-4.1 supports only HASH indexes on HEAP @@ -1852,96 +2302,137 @@ sub _d { { package VersionParser; -use strict; -use warnings FATAL => 'all'; +use Mo; +use Scalar::Util qw(blessed); use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; -sub new { - my ( $class ) = @_; - bless {}, $class; -} +use overload ( + '""' => "version", + '<=>' => "cmp", + 'cmp' => "cmp", + fallback => 1, +); -sub parse { - my ( $self, $str ) = @_; - 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; -} +our $VERSION = 0.01; -sub version_cmp { - my ($self, $dbh, $target, $cmp) = @_; - my $version = $self->version($dbh); - my $result; +has major => ( + is => 'ro', + isa => 'Int', + required => 1, +); - 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" - } +has [qw( minor revision )] => ( + is => 'ro', + isa => 'Num', +); - PTDEBUG && _d($self->{$dbh}, $cmp, $target, ':', $result); - return $result; -} +has flavor => ( + is => 'ro', + isa => 'Str', + default => sub { 'Unknown' }, +); -sub version_ge { - my ( $self, $dbh, $target ) = @_; - return $self->version_cmp($dbh, $target, 'ge'); -} +has innodb_version => ( + is => 'ro', + isa => 'Str', + default => sub { 'NO' }, +); -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 series { + my $self = shift; + return $self->_join_version($self->major, $self->minor); } sub version { - my ( $self, $dbh ) = @_; - if ( !$self->{$dbh} ) { - $self->{$dbh} = $self->parse( - $dbh->selectrow_array('SELECT VERSION()')); - } - return $self->{$dbh}; + my $self = shift; + return $self->_join_version($self->major, $self->minor, $self->revision); } -sub innodb_version { +sub is_in { + my ($self, $target) = @_; + + return $self eq $target; +} + +sub _join_version { + my ($self, @parts) = @_; + + return join ".", map { my $c = $_; $c =~ s/^0\./0/; $c } grep defined, @parts; +} +sub _split_version { + my ($self, $str) = @_; + my @version_parts = map { s/^0(?=\d)/0./; $_ } $str =~ m/(\d+)/g; + return @version_parts[0..2]; +} + +sub normalized_version { + my ( $self ) = @_; + my @version_parts = map { $_ || 0 } $self->_split_version( $self->version ); + my $result = sprintf('%d%02d%02d', @version_parts); + PTDEBUG && _d($self->version, 'normalizes to', $result); + return $result; +} + +sub comment { + my ( $self, $cmd ) = @_; + my $v = $self->normalized_version(); + + return "/*!$v $cmd */" +} + +my @methods = qw(major minor revision); +sub cmp { + my ($left, $right) = @_; + my $right_obj = (blessed($right) && $right->isa(ref($left))) + ? $right + : ref($left)->new($right); + + my $retval = 0; + for my $m ( @methods ) { + last unless defined($left->$m) && defined($right_obj->$m); + $retval = $left->$m <=> $right_obj->$m; + last if $retval; + } + return $retval; +} + +sub BUILDARGS { + my $self = shift; + + if ( @_ == 1 ) { + my %args; + if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { + my $dbh = $_[0]; + my $query; + PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); + if ( eval { $query = $dbh->selectall_hashref(q) } ) { + @args{@methods} = $self->_split_version($query->{version}); + $args{flavor} = delete $query->{version_comment} + if $query->{version_comment}; + } + elsif ( eval { ($query) = $dbh->selectrow_array('SELECT VERSION()') } ) { + @args{@methods} = $self->_split_version($query); + } + else { + PTDEBUG && _d("Couldn't get the version from the dbh: $@"); + } + $args{innodb_version} = eval { $self->_innodb_version($dbh) }; + } + elsif ( !ref($_[0]) ) { + @args{@methods} = $self->_split_version($_[0]); + } + + for my $method (@methods) { + delete $args{$method} unless defined $args{$method}; + } + @_ = %args if %args; + } + + return $self->SUPER::BUILDARGS(@_); +} + +sub _innodb_version { my ( $self, $dbh ) = @_; return unless $dbh; my $innodb_version = "NO"; @@ -1979,6 +2470,7 @@ sub _d { print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; } +no Mo; 1; } # ########################################################################### @@ -2770,8 +3262,7 @@ sub get_connected_slaves { my $show = "SHOW GRANTS FOR "; my $user = 'CURRENT_USER()'; - my $vp = $self->{VersionParser}; - if ( $vp && !$vp->version_ge($dbh, '4.1.2') ) { + if ( VersionParser->new($dbh) < '4.1.2' ) { $user = $dbh->selectrow_arrayref('SELECT USER()')->[0]; $user =~ s/([^@]+)@(.+)/'$1'\@'$2'/; } @@ -3298,7 +3789,6 @@ my ( $src, $dst ); # Holds the arguments for the $sth's bind variables, so it can be re-tried # easily. my @beginning_of_txn; -my $vp = new VersionParser; my $q = new Quoter; sub main { @@ -3554,7 +4044,7 @@ sub main { my $dsn_defaults = $dp->parse_options($o); my $dsn = $dp->parse($o->get('check-slave-lag'), $dsn_defaults); $lag_dbh = $dp->get_dbh($dp->get_cxn_params($dsn), { AutoCommit => 1 }); - $ms = new MasterSlave(VersionParser => $vp); + $ms = new MasterSlave(); } # ######################################################################## @@ -3627,7 +4117,7 @@ sub main { . join(',', map { $q->quote($_) } @{$sel_stmt->{cols}} ) . " FROM $src->{db_tbl}" . ( $sel_stmt->{index} - ? (($vp->version_ge($dbh, '4.0.9') ? " FORCE" : " USE") + ? ((VersionParser->new($dbh) >= '4.0.9' ? " FORCE" : " USE") . " INDEX(`$sel_stmt->{index}`)") : '') . " WHERE (".$o->get('where').")"; @@ -4327,7 +4817,7 @@ sub ts { sub get_irot { my ( $dbh ) = @_; - return 1 unless $vp->version_ge($dbh, '5.0.13'); + return 1 unless VersionParser->new($dbh) >= '5.0.13'; my $rows = $dbh->selectall_arrayref( "show variables like 'innodb_rollback_on_timeout'", { Slice => {} }); diff --git a/bin/pt-deadlock-logger b/bin/pt-deadlock-logger index 584c0f87..c2017a2f 100755 --- a/bin/pt-deadlock-logger +++ b/bin/pt-deadlock-logger @@ -1033,6 +1033,455 @@ if ( PTDEBUG ) { # End OptionParser package # ########################################################################### +# ########################################################################### +# Mo 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/Mo.pm +# t/lib/Mo.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +BEGIN { +$INC{"Mo.pm"} = __FILE__; +package Mo; +our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. + +{ + no strict 'refs'; + sub _glob_for { + return \*{shift()} + } + + sub _stash_for { + return \%{ shift() . "::" }; + } +} + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util (); + +our %TYPES = ( + Bool => sub { !$_[0] || (defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == 1) }, + Num => sub { defined $_[0] && &Scalar::Util::looks_like_number }, + Int => sub { defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == int $_[0] }, + Str => sub { defined $_[0] }, + Object => sub { defined $_[0] && &Scalar::Util::blessed }, + FileHandle => sub { local $@; require IO::Handle; fileno($_[0]) && $_[0]->opened }, + + map { + my $type = /R/ ? $_ : uc $_; + $_ . "Ref" => sub { ref $_[0] eq $type } + } qw(Array Code Hash Regexp Glob Scalar) +); + +our %metadata_for; +{ + package Mo::Object; + + sub new { + my $class = shift; + my $args = $class->BUILDARGS(@_); + + my @args_to_delete; + while ( my ($attr, $meta) = each %{$metadata_for{$class}} ) { + next unless exists $meta->{init_arg}; + my $init_arg = $meta->{init_arg}; + + if ( defined $init_arg ) { + $args->{$attr} = delete $args->{$init_arg}; + } + else { + push @args_to_delete, $attr; + } + } + + delete $args->{$_} for @args_to_delete; + + for my $attribute ( keys %$args ) { + if ( my $coerce = $metadata_for{$class}{$attribute}{coerce} ) { + $args->{$attribute} = $coerce->($args->{$attribute}); + } + if ( my $I = $metadata_for{$class}{$attribute}{isa} ) { + ( (my $I_name), $I ) = @{$I}; + Mo::_check_type_constaints($attribute, $I, $I_name, $args->{$attribute}); + } + } + + while ( my ($attribute, $meta) = each %{$metadata_for{$class}} ) { + next unless $meta->{required}; + Carp::confess("Attribute ($attribute) is required for $class") + if ! exists $args->{$attribute} + } + + @_ = %$args; + my $self = bless $args, $class; + + my @build_subs; + my $linearized_isa = mro::get_linear_isa($class); + + for my $isa_class ( @$linearized_isa ) { + unshift @build_subs, *{ Mo::_glob_for "${isa_class}::BUILD" }{CODE}; + } + exists &$_ && $_->( $self, @_ ) for grep { defined } @build_subs; + return $self; + } + + sub BUILDARGS { + shift; + my $ref; + if ( @_ == 1 && ref($_[0]) ) { + Carp::confess("Single parameters to new() must be a HASH ref") + unless ref($_[0]) eq ref({}); + $ref = {%{$_[0]}} # We want a new reference, always + } + else { + $ref = { @_ }; + } + return $ref; + } +} + +my %export_for; +sub Mo::import { + warnings->import(qw(FATAL all)); + strict->import(); + + my $caller = scalar caller(); # Caller's package + my $caller_pkg = $caller . "::"; # Caller's package with :: at the end + my (%exports, %options); + + my (undef, @features) = @_; + my %ignore = ( map { $_ => 1 } qw( is isa init_arg builder buildargs clearer predicate build handles default required ) ); + for my $feature (grep { !$ignore{$_} } @features) { + { local $@; require "Mo/$feature.pm"; } + { + no strict 'refs'; + &{"Mo::${feature}::e"}( + $caller_pkg, + \%exports, + \%options, + \@_ + ); + } + } + + return if $exports{M}; + + %exports = ( + extends => sub { + for my $class ( map { "$_" } @_ ) { + $class =~ s{::|'}{/}g; + { local $@; eval { require "$class.pm" } } # or warn $@; + } + _set_package_isa($caller, @_); + _set_inherited_metadata($caller); + }, + has => sub { + my $names = shift; + for my $attribute ( ref $names ? @$names : $names ) { + my %args = @_; + my $method = ($args{is} || '') eq 'ro' + ? sub { + Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller_pkg}${attribute}") + if $#_; + return $_[0]{$attribute}; + } + : sub { + return $#_ + ? $_[0]{$attribute} = $_[1] + : $_[0]{$attribute}; + }; + + $metadata_for{$caller}{$attribute} = (); + + if ( my $I = $args{isa} ) { + my $orig_I = $I; + my $type; + if ( $I =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $I = _nested_constraints($attribute, $1, $2); + } + $metadata_for{$caller}{$attribute}{isa} = [$orig_I, $I]; + my $orig_method = $method; + $method = sub { + if ( $#_ ) { + Mo::_check_type_constaints($attribute, $I, $orig_I, $_[1]); + } + goto &$orig_method; + }; + } + + if ( my $builder = $args{builder} ) { + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$builder + : goto &$original_method + }; + } + + if ( my $code = $args{default} ) { + Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") + unless ref($code) eq 'CODE'; + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$code + : goto &$original_method + }; + } + + if ( my $role = $args{does} ) { + my $original_method = $method; + $method = sub { + if ( $#_ ) { + Carp::confess(qq) + unless blessed($_[1]) && $_[1]->does($role) + } + goto &$original_method + }; + } + + if ( my $coercion = $args{coerce} ) { + $metadata_for{$caller}{$attribute}{coerce} = $coercion; + my $original_method = $method; + $method = sub { + if ( $#_ ) { + return $original_method->($_[0], $coercion->($_[1])) + } + goto &$original_method; + } + } + + $method = $options{$_}->($method, $attribute, @_) + for sort keys %options; + + *{ _glob_for "${caller}::$attribute" } = $method; + + if ( $args{required} ) { + $metadata_for{$caller}{$attribute}{required} = 1; + } + + if ($args{clearer}) { + *{ _glob_for "${caller}::$args{clearer}" } + = sub { delete shift->{$attribute} } + } + + if ($args{predicate}) { + *{ _glob_for "${caller}::$args{predicate}" } + = sub { exists shift->{$attribute} } + } + + if ($args{handles}) { + _has_handles($caller, $attribute, \%args); + } + + if (exists $args{init_arg}) { + $metadata_for{$caller}{$attribute}{init_arg} = $args{init_arg}; + } + } + }, + %exports, + ); + + $export_for{$caller} = [ keys %exports ]; + + for my $keyword ( keys %exports ) { + *{ _glob_for "${caller}::$keyword" } = $exports{$keyword} + } + *{ _glob_for "${caller}::extends" }{CODE}->( "Mo::Object" ) + unless @{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] }; +}; + +sub _check_type_constaints { + my ($attribute, $I, $I_name, $val) = @_; + ( ref($I) eq 'CODE' + ? $I->($val) + : (ref $val eq $I + || ($val && $val eq $I) + || (exists $TYPES{$I} && $TYPES{$I}->($val))) + ) + || Carp::confess( + qq + . qq + . (defined $val ? Mo::Dumper($val) : 'undef') ) +} + +sub _has_handles { + my ($caller, $attribute, $args) = @_; + my $handles = $args->{handles}; + + my $ref = ref $handles; + my $kv; + if ( $ref eq ref [] ) { + $kv = { map { $_,$_ } @{$handles} }; + } + elsif ( $ref eq ref {} ) { + $kv = $handles; + } + elsif ( $ref eq ref qr// ) { + Carp::confess("Cannot delegate methods based on a Regexp without a type constraint (isa)") + unless $args->{isa}; + my $target_class = $args->{isa}; + $kv = { + map { $_, $_ } + grep { $_ =~ $handles } + grep { !exists $Mo::Object::{$_} && $target_class->can($_) } + grep { $_ ne 'has' && $_ ne 'extends' } + keys %{ _stash_for $target_class } + }; + } + else { + Carp::confess("handles for $ref not yet implemented"); + } + + while ( my ($method, $target) = each %{$kv} ) { + my $name = _glob_for "${caller}::$method"; + Carp::confess("You cannot overwrite a locally defined method ($method) with a delegation") + if defined &$name; + + my ($target, @curried_args) = ref($target) ? @$target : $target; + *$name = sub { + my $self = shift; + my $delegate_to = $self->$attribute(); + my $error = "Cannot delegate $method to $target because the value of $attribute"; + Carp::confess("$error is not defined") unless $delegate_to; + Carp::confess("$error is not an object (got '$delegate_to')") + unless Scalar::Util::blessed($delegate_to) || (!ref($delegate_to) && $delegate_to->can($target)); + return $delegate_to->$target(@curried_args, @_); + } + } +} + +sub _nested_constraints { + my ($attribute, $aggregate_type, $type) = @_; + + my $inner_types; + if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $inner_types = _nested_constraints($1, $2); + } + else { + $inner_types = $TYPES{$type}; + } + + if ( $aggregate_type eq 'ArrayRef' ) { + return sub { + my ($val) = @_; + return unless ref($val) eq ref([]); + + if ($inner_types) { + for my $value ( @{$val} ) { + return unless $inner_types->($value) + } + } + else { + for my $value ( @{$val} ) { + return unless $value && ($value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type))); + } + } + return 1; + }; + } + elsif ( $aggregate_type eq 'Maybe' ) { + return sub { + my ($value) = @_; + return 1 if ! defined($value); + if ($inner_types) { + return unless $inner_types->($value) + } + else { + return unless $value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type)); + } + return 1; + } + } + else { + Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); + } +} + +sub _set_package_isa { + my ($package, @new_isa) = @_; + + *{ _glob_for "${package}::ISA" } = [@new_isa]; +} + +sub _set_inherited_metadata { + my $class = shift; + my $linearized_isa = mro::get_linear_isa($class); + my %new_metadata; + + for my $isa_class (reverse @$linearized_isa) { + %new_metadata = ( + %new_metadata, + %{ $metadata_for{$isa_class} || {} }, + ); + } + $metadata_for{$class} = \%new_metadata; +} + +sub unimport { + my $caller = scalar caller(); + my $stash = _stash_for( $caller ); + + delete $stash->{$_} for @{$export_for{$caller}}; +} + +sub Dumper { + require Data::Dumper; + local $Data::Dumper::Indent = 0; + local $Data::Dumper::Sortkeys = 0; + local $Data::Dumper::Quotekeys = 0; + local $Data::Dumper::Terse = 1; + + Data::Dumper::Dumper(@_) +} + +BEGIN { + if ($] >= 5.010) { + { local $@; require mro; } + } + else { + local $@; + eval { + require MRO::Compat; + } or do { + *mro::get_linear_isa = *mro::get_linear_isa_dfs = sub { + no strict 'refs'; + + my $classname = shift; + + my @lin = ($classname); + my %stored; + foreach my $parent (@{"$classname\::ISA"}) { + my $plin = mro::get_linear_isa_dfs($parent); + foreach (@$plin) { + next if exists $stored{$_}; + push(@lin, $_); + $stored{$_} = 1; + } + } + return \@lin; + }; + } + } +} + +} +1; +} +# ########################################################################### +# End Mo package +# ########################################################################### # ########################################################################### # VersionParser package # This package is a copy without comments from the original. The original @@ -1044,96 +1493,137 @@ if ( PTDEBUG ) { { package VersionParser; -use strict; -use warnings FATAL => 'all'; +use Mo; +use Scalar::Util qw(blessed); use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; -sub new { - my ( $class ) = @_; - bless {}, $class; -} +use overload ( + '""' => "version", + '<=>' => "cmp", + 'cmp' => "cmp", + fallback => 1, +); -sub parse { - my ( $self, $str ) = @_; - 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; -} +our $VERSION = 0.01; -sub version_cmp { - my ($self, $dbh, $target, $cmp) = @_; - my $version = $self->version($dbh); - my $result; +has major => ( + is => 'ro', + isa => 'Int', + required => 1, +); - 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" - } +has [qw( minor revision )] => ( + is => 'ro', + isa => 'Num', +); - PTDEBUG && _d($self->{$dbh}, $cmp, $target, ':', $result); - return $result; -} +has flavor => ( + is => 'ro', + isa => 'Str', + default => sub { 'Unknown' }, +); -sub version_ge { - my ( $self, $dbh, $target ) = @_; - return $self->version_cmp($dbh, $target, 'ge'); -} +has innodb_version => ( + is => 'ro', + isa => 'Str', + default => sub { 'NO' }, +); -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 series { + my $self = shift; + return $self->_join_version($self->major, $self->minor); } sub version { - my ( $self, $dbh ) = @_; - if ( !$self->{$dbh} ) { - $self->{$dbh} = $self->parse( - $dbh->selectrow_array('SELECT VERSION()')); - } - return $self->{$dbh}; + my $self = shift; + return $self->_join_version($self->major, $self->minor, $self->revision); } -sub innodb_version { +sub is_in { + my ($self, $target) = @_; + + return $self eq $target; +} + +sub _join_version { + my ($self, @parts) = @_; + + return join ".", map { my $c = $_; $c =~ s/^0\./0/; $c } grep defined, @parts; +} +sub _split_version { + my ($self, $str) = @_; + my @version_parts = map { s/^0(?=\d)/0./; $_ } $str =~ m/(\d+)/g; + return @version_parts[0..2]; +} + +sub normalized_version { + my ( $self ) = @_; + my @version_parts = map { $_ || 0 } $self->_split_version( $self->version ); + my $result = sprintf('%d%02d%02d', @version_parts); + PTDEBUG && _d($self->version, 'normalizes to', $result); + return $result; +} + +sub comment { + my ( $self, $cmd ) = @_; + my $v = $self->normalized_version(); + + return "/*!$v $cmd */" +} + +my @methods = qw(major minor revision); +sub cmp { + my ($left, $right) = @_; + my $right_obj = (blessed($right) && $right->isa(ref($left))) + ? $right + : ref($left)->new($right); + + my $retval = 0; + for my $m ( @methods ) { + last unless defined($left->$m) && defined($right_obj->$m); + $retval = $left->$m <=> $right_obj->$m; + last if $retval; + } + return $retval; +} + +sub BUILDARGS { + my $self = shift; + + if ( @_ == 1 ) { + my %args; + if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { + my $dbh = $_[0]; + my $query; + PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); + if ( eval { $query = $dbh->selectall_hashref(q) } ) { + @args{@methods} = $self->_split_version($query->{version}); + $args{flavor} = delete $query->{version_comment} + if $query->{version_comment}; + } + elsif ( eval { ($query) = $dbh->selectrow_array('SELECT VERSION()') } ) { + @args{@methods} = $self->_split_version($query); + } + else { + PTDEBUG && _d("Couldn't get the version from the dbh: $@"); + } + $args{innodb_version} = eval { $self->_innodb_version($dbh) }; + } + elsif ( !ref($_[0]) ) { + @args{@methods} = $self->_split_version($_[0]); + } + + for my $method (@methods) { + delete $args{$method} unless defined $args{$method}; + } + @_ = %args if %args; + } + + return $self->SUPER::BUILDARGS(@_); +} + +sub _innodb_version { my ( $self, $dbh ) = @_; return unless $dbh; my $innodb_version = "NO"; @@ -1171,6 +1661,7 @@ sub _d { print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; } +no Mo; 1; } # ########################################################################### @@ -1919,7 +2410,6 @@ sub main { @ARGV = @_; # set global ARGV for this package my $q = new Quoter(); - my $vp = new VersionParser(); # ######################################################################## # Get configuration information. @@ -2072,7 +2562,7 @@ sub main { $dbh->{AutoCommit} = 0; my $sql = $o->read_para_after(__FILE__, qr/MAGIC_clear_deadlocks/); - if ( !$vp->version_ge($dbh, '4.1.2') ) { + if ( VersionParser->new($dbh) < '4.1.2') { $sql =~ s/ENGINE=/TYPE=/; } $sql =~ s/test.deadlock_maker/$db_tbl/; diff --git a/bin/pt-duplicate-key-checker b/bin/pt-duplicate-key-checker index ef52152b..753703be 100755 --- a/bin/pt-duplicate-key-checker +++ b/bin/pt-duplicate-key-checker @@ -8,6 +8,456 @@ use strict; use warnings FATAL => 'all'; use constant PTDEBUG => $ENV{PTDEBUG} || 0; +# ########################################################################### +# Mo 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/Mo.pm +# t/lib/Mo.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +BEGIN { +$INC{"Mo.pm"} = __FILE__; +package Mo; +our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. + +{ + no strict 'refs'; + sub _glob_for { + return \*{shift()} + } + + sub _stash_for { + return \%{ shift() . "::" }; + } +} + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util (); + +our %TYPES = ( + Bool => sub { !$_[0] || (defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == 1) }, + Num => sub { defined $_[0] && &Scalar::Util::looks_like_number }, + Int => sub { defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == int $_[0] }, + Str => sub { defined $_[0] }, + Object => sub { defined $_[0] && &Scalar::Util::blessed }, + FileHandle => sub { local $@; require IO::Handle; fileno($_[0]) && $_[0]->opened }, + + map { + my $type = /R/ ? $_ : uc $_; + $_ . "Ref" => sub { ref $_[0] eq $type } + } qw(Array Code Hash Regexp Glob Scalar) +); + +our %metadata_for; +{ + package Mo::Object; + + sub new { + my $class = shift; + my $args = $class->BUILDARGS(@_); + + my @args_to_delete; + while ( my ($attr, $meta) = each %{$metadata_for{$class}} ) { + next unless exists $meta->{init_arg}; + my $init_arg = $meta->{init_arg}; + + if ( defined $init_arg ) { + $args->{$attr} = delete $args->{$init_arg}; + } + else { + push @args_to_delete, $attr; + } + } + + delete $args->{$_} for @args_to_delete; + + for my $attribute ( keys %$args ) { + if ( my $coerce = $metadata_for{$class}{$attribute}{coerce} ) { + $args->{$attribute} = $coerce->($args->{$attribute}); + } + if ( my $I = $metadata_for{$class}{$attribute}{isa} ) { + ( (my $I_name), $I ) = @{$I}; + Mo::_check_type_constaints($attribute, $I, $I_name, $args->{$attribute}); + } + } + + while ( my ($attribute, $meta) = each %{$metadata_for{$class}} ) { + next unless $meta->{required}; + Carp::confess("Attribute ($attribute) is required for $class") + if ! exists $args->{$attribute} + } + + @_ = %$args; + my $self = bless $args, $class; + + my @build_subs; + my $linearized_isa = mro::get_linear_isa($class); + + for my $isa_class ( @$linearized_isa ) { + unshift @build_subs, *{ Mo::_glob_for "${isa_class}::BUILD" }{CODE}; + } + exists &$_ && $_->( $self, @_ ) for grep { defined } @build_subs; + return $self; + } + + sub BUILDARGS { + shift; + my $ref; + if ( @_ == 1 && ref($_[0]) ) { + Carp::confess("Single parameters to new() must be a HASH ref") + unless ref($_[0]) eq ref({}); + $ref = {%{$_[0]}} # We want a new reference, always + } + else { + $ref = { @_ }; + } + return $ref; + } +} + +my %export_for; +sub Mo::import { + warnings->import(qw(FATAL all)); + strict->import(); + + my $caller = scalar caller(); # Caller's package + my $caller_pkg = $caller . "::"; # Caller's package with :: at the end + my (%exports, %options); + + my (undef, @features) = @_; + my %ignore = ( map { $_ => 1 } qw( is isa init_arg builder buildargs clearer predicate build handles default required ) ); + for my $feature (grep { !$ignore{$_} } @features) { + { local $@; require "Mo/$feature.pm"; } + { + no strict 'refs'; + &{"Mo::${feature}::e"}( + $caller_pkg, + \%exports, + \%options, + \@_ + ); + } + } + + return if $exports{M}; + + %exports = ( + extends => sub { + for my $class ( map { "$_" } @_ ) { + $class =~ s{::|'}{/}g; + { local $@; eval { require "$class.pm" } } # or warn $@; + } + _set_package_isa($caller, @_); + _set_inherited_metadata($caller); + }, + has => sub { + my $names = shift; + for my $attribute ( ref $names ? @$names : $names ) { + my %args = @_; + my $method = ($args{is} || '') eq 'ro' + ? sub { + Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller_pkg}${attribute}") + if $#_; + return $_[0]{$attribute}; + } + : sub { + return $#_ + ? $_[0]{$attribute} = $_[1] + : $_[0]{$attribute}; + }; + + $metadata_for{$caller}{$attribute} = (); + + if ( my $I = $args{isa} ) { + my $orig_I = $I; + my $type; + if ( $I =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $I = _nested_constraints($attribute, $1, $2); + } + $metadata_for{$caller}{$attribute}{isa} = [$orig_I, $I]; + my $orig_method = $method; + $method = sub { + if ( $#_ ) { + Mo::_check_type_constaints($attribute, $I, $orig_I, $_[1]); + } + goto &$orig_method; + }; + } + + if ( my $builder = $args{builder} ) { + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$builder + : goto &$original_method + }; + } + + if ( my $code = $args{default} ) { + Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") + unless ref($code) eq 'CODE'; + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$code + : goto &$original_method + }; + } + + if ( my $role = $args{does} ) { + my $original_method = $method; + $method = sub { + if ( $#_ ) { + Carp::confess(qq) + unless blessed($_[1]) && $_[1]->does($role) + } + goto &$original_method + }; + } + + if ( my $coercion = $args{coerce} ) { + $metadata_for{$caller}{$attribute}{coerce} = $coercion; + my $original_method = $method; + $method = sub { + if ( $#_ ) { + return $original_method->($_[0], $coercion->($_[1])) + } + goto &$original_method; + } + } + + $method = $options{$_}->($method, $attribute, @_) + for sort keys %options; + + *{ _glob_for "${caller}::$attribute" } = $method; + + if ( $args{required} ) { + $metadata_for{$caller}{$attribute}{required} = 1; + } + + if ($args{clearer}) { + *{ _glob_for "${caller}::$args{clearer}" } + = sub { delete shift->{$attribute} } + } + + if ($args{predicate}) { + *{ _glob_for "${caller}::$args{predicate}" } + = sub { exists shift->{$attribute} } + } + + if ($args{handles}) { + _has_handles($caller, $attribute, \%args); + } + + if (exists $args{init_arg}) { + $metadata_for{$caller}{$attribute}{init_arg} = $args{init_arg}; + } + } + }, + %exports, + ); + + $export_for{$caller} = [ keys %exports ]; + + for my $keyword ( keys %exports ) { + *{ _glob_for "${caller}::$keyword" } = $exports{$keyword} + } + *{ _glob_for "${caller}::extends" }{CODE}->( "Mo::Object" ) + unless @{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] }; +}; + +sub _check_type_constaints { + my ($attribute, $I, $I_name, $val) = @_; + ( ref($I) eq 'CODE' + ? $I->($val) + : (ref $val eq $I + || ($val && $val eq $I) + || (exists $TYPES{$I} && $TYPES{$I}->($val))) + ) + || Carp::confess( + qq + . qq + . (defined $val ? Mo::Dumper($val) : 'undef') ) +} + +sub _has_handles { + my ($caller, $attribute, $args) = @_; + my $handles = $args->{handles}; + + my $ref = ref $handles; + my $kv; + if ( $ref eq ref [] ) { + $kv = { map { $_,$_ } @{$handles} }; + } + elsif ( $ref eq ref {} ) { + $kv = $handles; + } + elsif ( $ref eq ref qr// ) { + Carp::confess("Cannot delegate methods based on a Regexp without a type constraint (isa)") + unless $args->{isa}; + my $target_class = $args->{isa}; + $kv = { + map { $_, $_ } + grep { $_ =~ $handles } + grep { !exists $Mo::Object::{$_} && $target_class->can($_) } + grep { $_ ne 'has' && $_ ne 'extends' } + keys %{ _stash_for $target_class } + }; + } + else { + Carp::confess("handles for $ref not yet implemented"); + } + + while ( my ($method, $target) = each %{$kv} ) { + my $name = _glob_for "${caller}::$method"; + Carp::confess("You cannot overwrite a locally defined method ($method) with a delegation") + if defined &$name; + + my ($target, @curried_args) = ref($target) ? @$target : $target; + *$name = sub { + my $self = shift; + my $delegate_to = $self->$attribute(); + my $error = "Cannot delegate $method to $target because the value of $attribute"; + Carp::confess("$error is not defined") unless $delegate_to; + Carp::confess("$error is not an object (got '$delegate_to')") + unless Scalar::Util::blessed($delegate_to) || (!ref($delegate_to) && $delegate_to->can($target)); + return $delegate_to->$target(@curried_args, @_); + } + } +} + +sub _nested_constraints { + my ($attribute, $aggregate_type, $type) = @_; + + my $inner_types; + if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $inner_types = _nested_constraints($1, $2); + } + else { + $inner_types = $TYPES{$type}; + } + + if ( $aggregate_type eq 'ArrayRef' ) { + return sub { + my ($val) = @_; + return unless ref($val) eq ref([]); + + if ($inner_types) { + for my $value ( @{$val} ) { + return unless $inner_types->($value) + } + } + else { + for my $value ( @{$val} ) { + return unless $value && ($value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type))); + } + } + return 1; + }; + } + elsif ( $aggregate_type eq 'Maybe' ) { + return sub { + my ($value) = @_; + return 1 if ! defined($value); + if ($inner_types) { + return unless $inner_types->($value) + } + else { + return unless $value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type)); + } + return 1; + } + } + else { + Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); + } +} + +sub _set_package_isa { + my ($package, @new_isa) = @_; + + *{ _glob_for "${package}::ISA" } = [@new_isa]; +} + +sub _set_inherited_metadata { + my $class = shift; + my $linearized_isa = mro::get_linear_isa($class); + my %new_metadata; + + for my $isa_class (reverse @$linearized_isa) { + %new_metadata = ( + %new_metadata, + %{ $metadata_for{$isa_class} || {} }, + ); + } + $metadata_for{$class} = \%new_metadata; +} + +sub unimport { + my $caller = scalar caller(); + my $stash = _stash_for( $caller ); + + delete $stash->{$_} for @{$export_for{$caller}}; +} + +sub Dumper { + require Data::Dumper; + local $Data::Dumper::Indent = 0; + local $Data::Dumper::Sortkeys = 0; + local $Data::Dumper::Quotekeys = 0; + local $Data::Dumper::Terse = 1; + + Data::Dumper::Dumper(@_) +} + +BEGIN { + if ($] >= 5.010) { + { local $@; require mro; } + } + else { + local $@; + eval { + require MRO::Compat; + } or do { + *mro::get_linear_isa = *mro::get_linear_isa_dfs = sub { + no strict 'refs'; + + my $classname = shift; + + my @lin = ($classname); + my %stored; + foreach my $parent (@{"$classname\::ISA"}) { + my $plin = mro::get_linear_isa_dfs($parent); + foreach (@$plin) { + next if exists $stored{$_}; + push(@lin, $_); + $stored{$_} = 1; + } + } + return \@lin; + }; + } + } +} + +} +1; +} +# ########################################################################### +# End Mo package +# ########################################################################### + # ########################################################################### # VersionParser package # This package is a copy without comments from the original. The original @@ -19,96 +469,137 @@ use constant PTDEBUG => $ENV{PTDEBUG} || 0; { package VersionParser; -use strict; -use warnings FATAL => 'all'; +use Mo; +use Scalar::Util qw(blessed); use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; -sub new { - my ( $class ) = @_; - bless {}, $class; -} +use overload ( + '""' => "version", + '<=>' => "cmp", + 'cmp' => "cmp", + fallback => 1, +); -sub parse { - my ( $self, $str ) = @_; - 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; -} +our $VERSION = 0.01; -sub version_cmp { - my ($self, $dbh, $target, $cmp) = @_; - my $version = $self->version($dbh); - my $result; +has major => ( + is => 'ro', + isa => 'Int', + required => 1, +); - 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" - } +has [qw( minor revision )] => ( + is => 'ro', + isa => 'Num', +); - PTDEBUG && _d($self->{$dbh}, $cmp, $target, ':', $result); - return $result; -} +has flavor => ( + is => 'ro', + isa => 'Str', + default => sub { 'Unknown' }, +); -sub version_ge { - my ( $self, $dbh, $target ) = @_; - return $self->version_cmp($dbh, $target, 'ge'); -} +has innodb_version => ( + is => 'ro', + isa => 'Str', + default => sub { 'NO' }, +); -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 series { + my $self = shift; + return $self->_join_version($self->major, $self->minor); } sub version { - my ( $self, $dbh ) = @_; - if ( !$self->{$dbh} ) { - $self->{$dbh} = $self->parse( - $dbh->selectrow_array('SELECT VERSION()')); - } - return $self->{$dbh}; + my $self = shift; + return $self->_join_version($self->major, $self->minor, $self->revision); } -sub innodb_version { +sub is_in { + my ($self, $target) = @_; + + return $self eq $target; +} + +sub _join_version { + my ($self, @parts) = @_; + + return join ".", map { my $c = $_; $c =~ s/^0\./0/; $c } grep defined, @parts; +} +sub _split_version { + my ($self, $str) = @_; + my @version_parts = map { s/^0(?=\d)/0./; $_ } $str =~ m/(\d+)/g; + return @version_parts[0..2]; +} + +sub normalized_version { + my ( $self ) = @_; + my @version_parts = map { $_ || 0 } $self->_split_version( $self->version ); + my $result = sprintf('%d%02d%02d', @version_parts); + PTDEBUG && _d($self->version, 'normalizes to', $result); + return $result; +} + +sub comment { + my ( $self, $cmd ) = @_; + my $v = $self->normalized_version(); + + return "/*!$v $cmd */" +} + +my @methods = qw(major minor revision); +sub cmp { + my ($left, $right) = @_; + my $right_obj = (blessed($right) && $right->isa(ref($left))) + ? $right + : ref($left)->new($right); + + my $retval = 0; + for my $m ( @methods ) { + last unless defined($left->$m) && defined($right_obj->$m); + $retval = $left->$m <=> $right_obj->$m; + last if $retval; + } + return $retval; +} + +sub BUILDARGS { + my $self = shift; + + if ( @_ == 1 ) { + my %args; + if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { + my $dbh = $_[0]; + my $query; + PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); + if ( eval { $query = $dbh->selectall_hashref(q) } ) { + @args{@methods} = $self->_split_version($query->{version}); + $args{flavor} = delete $query->{version_comment} + if $query->{version_comment}; + } + elsif ( eval { ($query) = $dbh->selectrow_array('SELECT VERSION()') } ) { + @args{@methods} = $self->_split_version($query); + } + else { + PTDEBUG && _d("Couldn't get the version from the dbh: $@"); + } + $args{innodb_version} = eval { $self->_innodb_version($dbh) }; + } + elsif ( !ref($_[0]) ) { + @args{@methods} = $self->_split_version($_[0]); + } + + for my $method (@methods) { + delete $args{$method} unless defined $args{$method}; + } + @_ = %args if %args; + } + + return $self->SUPER::BUILDARGS(@_); +} + +sub _innodb_version { my ( $self, $dbh ) = @_; return unless $dbh; my $innodb_version = "NO"; @@ -146,6 +637,7 @@ sub _d { print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; } +no Mo; 1; } # ########################################################################### @@ -575,7 +1067,7 @@ sub get_keys { my ( $type, $cols ) = $key =~ m/(?:USING (\w+))? \((.+)\)/; my ( $special ) = $key =~ m/(FULLTEXT|SPATIAL)/; $type = $type || $special || 'BTREE'; - if ( $opts->{mysql_version} && $opts->{mysql_version} lt '004001000' + if ( $opts->{mysql_version} && $opts->{mysql_version} lt '4.1' && $engine =~ m/HEAP|MEMORY/i ) { $type = 'HASH'; # MySQL pre-4.1 supports only HASH indexes on HEAP @@ -3517,8 +4009,7 @@ sub main { my $dbh = $dp->get_dbh($dp->get_cxn_params($dsn), { AutoCommit => 1, }); - my $vp = new VersionParser(); - my $version = $vp->parse($dbh->selectrow_array('SELECT VERSION()')); + my $version = VersionParser->new($dbh); # ####################################################################### # Do the main work. @@ -3550,7 +4041,7 @@ sub main { my ($keys, $clustered_key, $fks); if ( $get_keys ) { ($keys, $clustered_key) - = $tp->get_keys($tbl->{ddl}, {version => $version}); + = $tp->get_keys($tbl->{ddl}, {mysql_version => $version}); } if ( $get_fks ) { $fks = $tp->get_fks($tbl->{ddl}, {database => $tbl->{db}}); diff --git a/bin/pt-find b/bin/pt-find index 91fb1f4d..a12527b6 100755 --- a/bin/pt-find +++ b/bin/pt-find @@ -1394,6 +1394,456 @@ if ( PTDEBUG ) { # End OptionParser package # ########################################################################### +# ########################################################################### +# Mo 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/Mo.pm +# t/lib/Mo.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +BEGIN { +$INC{"Mo.pm"} = __FILE__; +package Mo; +our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. + +{ + no strict 'refs'; + sub _glob_for { + return \*{shift()} + } + + sub _stash_for { + return \%{ shift() . "::" }; + } +} + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util (); + +our %TYPES = ( + Bool => sub { !$_[0] || (defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == 1) }, + Num => sub { defined $_[0] && &Scalar::Util::looks_like_number }, + Int => sub { defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == int $_[0] }, + Str => sub { defined $_[0] }, + Object => sub { defined $_[0] && &Scalar::Util::blessed }, + FileHandle => sub { local $@; require IO::Handle; fileno($_[0]) && $_[0]->opened }, + + map { + my $type = /R/ ? $_ : uc $_; + $_ . "Ref" => sub { ref $_[0] eq $type } + } qw(Array Code Hash Regexp Glob Scalar) +); + +our %metadata_for; +{ + package Mo::Object; + + sub new { + my $class = shift; + my $args = $class->BUILDARGS(@_); + + my @args_to_delete; + while ( my ($attr, $meta) = each %{$metadata_for{$class}} ) { + next unless exists $meta->{init_arg}; + my $init_arg = $meta->{init_arg}; + + if ( defined $init_arg ) { + $args->{$attr} = delete $args->{$init_arg}; + } + else { + push @args_to_delete, $attr; + } + } + + delete $args->{$_} for @args_to_delete; + + for my $attribute ( keys %$args ) { + if ( my $coerce = $metadata_for{$class}{$attribute}{coerce} ) { + $args->{$attribute} = $coerce->($args->{$attribute}); + } + if ( my $I = $metadata_for{$class}{$attribute}{isa} ) { + ( (my $I_name), $I ) = @{$I}; + Mo::_check_type_constaints($attribute, $I, $I_name, $args->{$attribute}); + } + } + + while ( my ($attribute, $meta) = each %{$metadata_for{$class}} ) { + next unless $meta->{required}; + Carp::confess("Attribute ($attribute) is required for $class") + if ! exists $args->{$attribute} + } + + @_ = %$args; + my $self = bless $args, $class; + + my @build_subs; + my $linearized_isa = mro::get_linear_isa($class); + + for my $isa_class ( @$linearized_isa ) { + unshift @build_subs, *{ Mo::_glob_for "${isa_class}::BUILD" }{CODE}; + } + exists &$_ && $_->( $self, @_ ) for grep { defined } @build_subs; + return $self; + } + + sub BUILDARGS { + shift; + my $ref; + if ( @_ == 1 && ref($_[0]) ) { + Carp::confess("Single parameters to new() must be a HASH ref") + unless ref($_[0]) eq ref({}); + $ref = {%{$_[0]}} # We want a new reference, always + } + else { + $ref = { @_ }; + } + return $ref; + } +} + +my %export_for; +sub Mo::import { + warnings->import(qw(FATAL all)); + strict->import(); + + my $caller = scalar caller(); # Caller's package + my $caller_pkg = $caller . "::"; # Caller's package with :: at the end + my (%exports, %options); + + my (undef, @features) = @_; + my %ignore = ( map { $_ => 1 } qw( is isa init_arg builder buildargs clearer predicate build handles default required ) ); + for my $feature (grep { !$ignore{$_} } @features) { + { local $@; require "Mo/$feature.pm"; } + { + no strict 'refs'; + &{"Mo::${feature}::e"}( + $caller_pkg, + \%exports, + \%options, + \@_ + ); + } + } + + return if $exports{M}; + + %exports = ( + extends => sub { + for my $class ( map { "$_" } @_ ) { + $class =~ s{::|'}{/}g; + { local $@; eval { require "$class.pm" } } # or warn $@; + } + _set_package_isa($caller, @_); + _set_inherited_metadata($caller); + }, + has => sub { + my $names = shift; + for my $attribute ( ref $names ? @$names : $names ) { + my %args = @_; + my $method = ($args{is} || '') eq 'ro' + ? sub { + Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller_pkg}${attribute}") + if $#_; + return $_[0]{$attribute}; + } + : sub { + return $#_ + ? $_[0]{$attribute} = $_[1] + : $_[0]{$attribute}; + }; + + $metadata_for{$caller}{$attribute} = (); + + if ( my $I = $args{isa} ) { + my $orig_I = $I; + my $type; + if ( $I =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $I = _nested_constraints($attribute, $1, $2); + } + $metadata_for{$caller}{$attribute}{isa} = [$orig_I, $I]; + my $orig_method = $method; + $method = sub { + if ( $#_ ) { + Mo::_check_type_constaints($attribute, $I, $orig_I, $_[1]); + } + goto &$orig_method; + }; + } + + if ( my $builder = $args{builder} ) { + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$builder + : goto &$original_method + }; + } + + if ( my $code = $args{default} ) { + Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") + unless ref($code) eq 'CODE'; + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$code + : goto &$original_method + }; + } + + if ( my $role = $args{does} ) { + my $original_method = $method; + $method = sub { + if ( $#_ ) { + Carp::confess(qq) + unless blessed($_[1]) && $_[1]->does($role) + } + goto &$original_method + }; + } + + if ( my $coercion = $args{coerce} ) { + $metadata_for{$caller}{$attribute}{coerce} = $coercion; + my $original_method = $method; + $method = sub { + if ( $#_ ) { + return $original_method->($_[0], $coercion->($_[1])) + } + goto &$original_method; + } + } + + $method = $options{$_}->($method, $attribute, @_) + for sort keys %options; + + *{ _glob_for "${caller}::$attribute" } = $method; + + if ( $args{required} ) { + $metadata_for{$caller}{$attribute}{required} = 1; + } + + if ($args{clearer}) { + *{ _glob_for "${caller}::$args{clearer}" } + = sub { delete shift->{$attribute} } + } + + if ($args{predicate}) { + *{ _glob_for "${caller}::$args{predicate}" } + = sub { exists shift->{$attribute} } + } + + if ($args{handles}) { + _has_handles($caller, $attribute, \%args); + } + + if (exists $args{init_arg}) { + $metadata_for{$caller}{$attribute}{init_arg} = $args{init_arg}; + } + } + }, + %exports, + ); + + $export_for{$caller} = [ keys %exports ]; + + for my $keyword ( keys %exports ) { + *{ _glob_for "${caller}::$keyword" } = $exports{$keyword} + } + *{ _glob_for "${caller}::extends" }{CODE}->( "Mo::Object" ) + unless @{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] }; +}; + +sub _check_type_constaints { + my ($attribute, $I, $I_name, $val) = @_; + ( ref($I) eq 'CODE' + ? $I->($val) + : (ref $val eq $I + || ($val && $val eq $I) + || (exists $TYPES{$I} && $TYPES{$I}->($val))) + ) + || Carp::confess( + qq + . qq + . (defined $val ? Mo::Dumper($val) : 'undef') ) +} + +sub _has_handles { + my ($caller, $attribute, $args) = @_; + my $handles = $args->{handles}; + + my $ref = ref $handles; + my $kv; + if ( $ref eq ref [] ) { + $kv = { map { $_,$_ } @{$handles} }; + } + elsif ( $ref eq ref {} ) { + $kv = $handles; + } + elsif ( $ref eq ref qr// ) { + Carp::confess("Cannot delegate methods based on a Regexp without a type constraint (isa)") + unless $args->{isa}; + my $target_class = $args->{isa}; + $kv = { + map { $_, $_ } + grep { $_ =~ $handles } + grep { !exists $Mo::Object::{$_} && $target_class->can($_) } + grep { $_ ne 'has' && $_ ne 'extends' } + keys %{ _stash_for $target_class } + }; + } + else { + Carp::confess("handles for $ref not yet implemented"); + } + + while ( my ($method, $target) = each %{$kv} ) { + my $name = _glob_for "${caller}::$method"; + Carp::confess("You cannot overwrite a locally defined method ($method) with a delegation") + if defined &$name; + + my ($target, @curried_args) = ref($target) ? @$target : $target; + *$name = sub { + my $self = shift; + my $delegate_to = $self->$attribute(); + my $error = "Cannot delegate $method to $target because the value of $attribute"; + Carp::confess("$error is not defined") unless $delegate_to; + Carp::confess("$error is not an object (got '$delegate_to')") + unless Scalar::Util::blessed($delegate_to) || (!ref($delegate_to) && $delegate_to->can($target)); + return $delegate_to->$target(@curried_args, @_); + } + } +} + +sub _nested_constraints { + my ($attribute, $aggregate_type, $type) = @_; + + my $inner_types; + if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $inner_types = _nested_constraints($1, $2); + } + else { + $inner_types = $TYPES{$type}; + } + + if ( $aggregate_type eq 'ArrayRef' ) { + return sub { + my ($val) = @_; + return unless ref($val) eq ref([]); + + if ($inner_types) { + for my $value ( @{$val} ) { + return unless $inner_types->($value) + } + } + else { + for my $value ( @{$val} ) { + return unless $value && ($value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type))); + } + } + return 1; + }; + } + elsif ( $aggregate_type eq 'Maybe' ) { + return sub { + my ($value) = @_; + return 1 if ! defined($value); + if ($inner_types) { + return unless $inner_types->($value) + } + else { + return unless $value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type)); + } + return 1; + } + } + else { + Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); + } +} + +sub _set_package_isa { + my ($package, @new_isa) = @_; + + *{ _glob_for "${package}::ISA" } = [@new_isa]; +} + +sub _set_inherited_metadata { + my $class = shift; + my $linearized_isa = mro::get_linear_isa($class); + my %new_metadata; + + for my $isa_class (reverse @$linearized_isa) { + %new_metadata = ( + %new_metadata, + %{ $metadata_for{$isa_class} || {} }, + ); + } + $metadata_for{$class} = \%new_metadata; +} + +sub unimport { + my $caller = scalar caller(); + my $stash = _stash_for( $caller ); + + delete $stash->{$_} for @{$export_for{$caller}}; +} + +sub Dumper { + require Data::Dumper; + local $Data::Dumper::Indent = 0; + local $Data::Dumper::Sortkeys = 0; + local $Data::Dumper::Quotekeys = 0; + local $Data::Dumper::Terse = 1; + + Data::Dumper::Dumper(@_) +} + +BEGIN { + if ($] >= 5.010) { + { local $@; require mro; } + } + else { + local $@; + eval { + require MRO::Compat; + } or do { + *mro::get_linear_isa = *mro::get_linear_isa_dfs = sub { + no strict 'refs'; + + my $classname = shift; + + my @lin = ($classname); + my %stored; + foreach my $parent (@{"$classname\::ISA"}) { + my $plin = mro::get_linear_isa_dfs($parent); + foreach (@$plin) { + next if exists $stored{$_}; + push(@lin, $_); + $stored{$_} = 1; + } + } + return \@lin; + }; + } + } +} + +} +1; +} +# ########################################################################### +# End Mo package +# ########################################################################### + # ########################################################################### # Quoter package # This package is a copy without comments from the original. The original @@ -1524,96 +1974,137 @@ sub deserialize_list { { package VersionParser; -use strict; -use warnings FATAL => 'all'; +use Mo; +use Scalar::Util qw(blessed); use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; -sub new { - my ( $class ) = @_; - bless {}, $class; -} +use overload ( + '""' => "version", + '<=>' => "cmp", + 'cmp' => "cmp", + fallback => 1, +); -sub parse { - my ( $self, $str ) = @_; - 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; -} +our $VERSION = 0.01; -sub version_cmp { - my ($self, $dbh, $target, $cmp) = @_; - my $version = $self->version($dbh); - my $result; +has major => ( + is => 'ro', + isa => 'Int', + required => 1, +); - 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" - } +has [qw( minor revision )] => ( + is => 'ro', + isa => 'Num', +); - PTDEBUG && _d($self->{$dbh}, $cmp, $target, ':', $result); - return $result; -} +has flavor => ( + is => 'ro', + isa => 'Str', + default => sub { 'Unknown' }, +); -sub version_ge { - my ( $self, $dbh, $target ) = @_; - return $self->version_cmp($dbh, $target, 'ge'); -} +has innodb_version => ( + is => 'ro', + isa => 'Str', + default => sub { 'NO' }, +); -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 series { + my $self = shift; + return $self->_join_version($self->major, $self->minor); } sub version { - my ( $self, $dbh ) = @_; - if ( !$self->{$dbh} ) { - $self->{$dbh} = $self->parse( - $dbh->selectrow_array('SELECT VERSION()')); - } - return $self->{$dbh}; + my $self = shift; + return $self->_join_version($self->major, $self->minor, $self->revision); } -sub innodb_version { +sub is_in { + my ($self, $target) = @_; + + return $self eq $target; +} + +sub _join_version { + my ($self, @parts) = @_; + + return join ".", map { my $c = $_; $c =~ s/^0\./0/; $c } grep defined, @parts; +} +sub _split_version { + my ($self, $str) = @_; + my @version_parts = map { s/^0(?=\d)/0./; $_ } $str =~ m/(\d+)/g; + return @version_parts[0..2]; +} + +sub normalized_version { + my ( $self ) = @_; + my @version_parts = map { $_ || 0 } $self->_split_version( $self->version ); + my $result = sprintf('%d%02d%02d', @version_parts); + PTDEBUG && _d($self->version, 'normalizes to', $result); + return $result; +} + +sub comment { + my ( $self, $cmd ) = @_; + my $v = $self->normalized_version(); + + return "/*!$v $cmd */" +} + +my @methods = qw(major minor revision); +sub cmp { + my ($left, $right) = @_; + my $right_obj = (blessed($right) && $right->isa(ref($left))) + ? $right + : ref($left)->new($right); + + my $retval = 0; + for my $m ( @methods ) { + last unless defined($left->$m) && defined($right_obj->$m); + $retval = $left->$m <=> $right_obj->$m; + last if $retval; + } + return $retval; +} + +sub BUILDARGS { + my $self = shift; + + if ( @_ == 1 ) { + my %args; + if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { + my $dbh = $_[0]; + my $query; + PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); + if ( eval { $query = $dbh->selectall_hashref(q) } ) { + @args{@methods} = $self->_split_version($query->{version}); + $args{flavor} = delete $query->{version_comment} + if $query->{version_comment}; + } + elsif ( eval { ($query) = $dbh->selectrow_array('SELECT VERSION()') } ) { + @args{@methods} = $self->_split_version($query); + } + else { + PTDEBUG && _d("Couldn't get the version from the dbh: $@"); + } + $args{innodb_version} = eval { $self->_innodb_version($dbh) }; + } + elsif ( !ref($_[0]) ) { + @args{@methods} = $self->_split_version($_[0]); + } + + for my $method (@methods) { + delete $args{$method} unless defined $args{$method}; + } + @_ = %args if %args; + } + + return $self->SUPER::BUILDARGS(@_); +} + +sub _innodb_version { my ( $self, $dbh ) = @_; return unless $dbh; my $innodb_version = "NO"; @@ -1651,6 +2142,7 @@ sub _d { print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; } +no Mo; 1; } # ########################################################################### @@ -1961,7 +2453,7 @@ sub get_keys { my ( $type, $cols ) = $key =~ m/(?:USING (\w+))? \((.+)\)/; my ( $special ) = $key =~ m/(FULLTEXT|SPATIAL)/; $type = $type || $special || 'BTREE'; - if ( $opts->{mysql_version} && $opts->{mysql_version} lt '004001000' + if ( $opts->{mysql_version} && $opts->{mysql_version} lt '4.1' && $engine =~ m/HEAP|MEMORY/i ) { $type = 'HASH'; # MySQL pre-4.1 supports only HASH indexes on HEAP @@ -2689,8 +3181,7 @@ sub main { ($server_id) = $dbh->selectrow_array('SELECT @@SERVER_ID'); # Discover if we need to get stored code. Need dbh to do this. - my $vp = new VersionParser(); - my $need_stored_code = $vp->version_ge($dbh, '5.0.0'); + my $need_stored_code = VersionParser->new($dbh) >= '5.0.0'; $need_stored_code = grep { $o->got($_); } @stored_code_tests if $need_stored_code; PTDEBUG && _d('Need stored code:', $need_stored_code); diff --git a/bin/pt-heartbeat b/bin/pt-heartbeat index a56d8a22..b3a17667 100755 --- a/bin/pt-heartbeat +++ b/bin/pt-heartbeat @@ -222,8 +222,7 @@ sub get_connected_slaves { my $show = "SHOW GRANTS FOR "; my $user = 'CURRENT_USER()'; - my $vp = $self->{VersionParser}; - if ( $vp && !$vp->version_ge($dbh, '4.1.2') ) { + if ( VersionParser->new($dbh) < '4.1.2' ) { $user = $dbh->selectrow_arrayref('SELECT USER()')->[0]; $user =~ s/([^@]+)@(.+)/'$1'\@'$2'/; } @@ -1741,6 +1740,456 @@ if ( PTDEBUG ) { # End OptionParser package # ########################################################################### +# ########################################################################### +# Mo 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/Mo.pm +# t/lib/Mo.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +BEGIN { +$INC{"Mo.pm"} = __FILE__; +package Mo; +our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. + +{ + no strict 'refs'; + sub _glob_for { + return \*{shift()} + } + + sub _stash_for { + return \%{ shift() . "::" }; + } +} + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util (); + +our %TYPES = ( + Bool => sub { !$_[0] || (defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == 1) }, + Num => sub { defined $_[0] && &Scalar::Util::looks_like_number }, + Int => sub { defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == int $_[0] }, + Str => sub { defined $_[0] }, + Object => sub { defined $_[0] && &Scalar::Util::blessed }, + FileHandle => sub { local $@; require IO::Handle; fileno($_[0]) && $_[0]->opened }, + + map { + my $type = /R/ ? $_ : uc $_; + $_ . "Ref" => sub { ref $_[0] eq $type } + } qw(Array Code Hash Regexp Glob Scalar) +); + +our %metadata_for; +{ + package Mo::Object; + + sub new { + my $class = shift; + my $args = $class->BUILDARGS(@_); + + my @args_to_delete; + while ( my ($attr, $meta) = each %{$metadata_for{$class}} ) { + next unless exists $meta->{init_arg}; + my $init_arg = $meta->{init_arg}; + + if ( defined $init_arg ) { + $args->{$attr} = delete $args->{$init_arg}; + } + else { + push @args_to_delete, $attr; + } + } + + delete $args->{$_} for @args_to_delete; + + for my $attribute ( keys %$args ) { + if ( my $coerce = $metadata_for{$class}{$attribute}{coerce} ) { + $args->{$attribute} = $coerce->($args->{$attribute}); + } + if ( my $I = $metadata_for{$class}{$attribute}{isa} ) { + ( (my $I_name), $I ) = @{$I}; + Mo::_check_type_constaints($attribute, $I, $I_name, $args->{$attribute}); + } + } + + while ( my ($attribute, $meta) = each %{$metadata_for{$class}} ) { + next unless $meta->{required}; + Carp::confess("Attribute ($attribute) is required for $class") + if ! exists $args->{$attribute} + } + + @_ = %$args; + my $self = bless $args, $class; + + my @build_subs; + my $linearized_isa = mro::get_linear_isa($class); + + for my $isa_class ( @$linearized_isa ) { + unshift @build_subs, *{ Mo::_glob_for "${isa_class}::BUILD" }{CODE}; + } + exists &$_ && $_->( $self, @_ ) for grep { defined } @build_subs; + return $self; + } + + sub BUILDARGS { + shift; + my $ref; + if ( @_ == 1 && ref($_[0]) ) { + Carp::confess("Single parameters to new() must be a HASH ref") + unless ref($_[0]) eq ref({}); + $ref = {%{$_[0]}} # We want a new reference, always + } + else { + $ref = { @_ }; + } + return $ref; + } +} + +my %export_for; +sub Mo::import { + warnings->import(qw(FATAL all)); + strict->import(); + + my $caller = scalar caller(); # Caller's package + my $caller_pkg = $caller . "::"; # Caller's package with :: at the end + my (%exports, %options); + + my (undef, @features) = @_; + my %ignore = ( map { $_ => 1 } qw( is isa init_arg builder buildargs clearer predicate build handles default required ) ); + for my $feature (grep { !$ignore{$_} } @features) { + { local $@; require "Mo/$feature.pm"; } + { + no strict 'refs'; + &{"Mo::${feature}::e"}( + $caller_pkg, + \%exports, + \%options, + \@_ + ); + } + } + + return if $exports{M}; + + %exports = ( + extends => sub { + for my $class ( map { "$_" } @_ ) { + $class =~ s{::|'}{/}g; + { local $@; eval { require "$class.pm" } } # or warn $@; + } + _set_package_isa($caller, @_); + _set_inherited_metadata($caller); + }, + has => sub { + my $names = shift; + for my $attribute ( ref $names ? @$names : $names ) { + my %args = @_; + my $method = ($args{is} || '') eq 'ro' + ? sub { + Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller_pkg}${attribute}") + if $#_; + return $_[0]{$attribute}; + } + : sub { + return $#_ + ? $_[0]{$attribute} = $_[1] + : $_[0]{$attribute}; + }; + + $metadata_for{$caller}{$attribute} = (); + + if ( my $I = $args{isa} ) { + my $orig_I = $I; + my $type; + if ( $I =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $I = _nested_constraints($attribute, $1, $2); + } + $metadata_for{$caller}{$attribute}{isa} = [$orig_I, $I]; + my $orig_method = $method; + $method = sub { + if ( $#_ ) { + Mo::_check_type_constaints($attribute, $I, $orig_I, $_[1]); + } + goto &$orig_method; + }; + } + + if ( my $builder = $args{builder} ) { + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$builder + : goto &$original_method + }; + } + + if ( my $code = $args{default} ) { + Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") + unless ref($code) eq 'CODE'; + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$code + : goto &$original_method + }; + } + + if ( my $role = $args{does} ) { + my $original_method = $method; + $method = sub { + if ( $#_ ) { + Carp::confess(qq) + unless blessed($_[1]) && $_[1]->does($role) + } + goto &$original_method + }; + } + + if ( my $coercion = $args{coerce} ) { + $metadata_for{$caller}{$attribute}{coerce} = $coercion; + my $original_method = $method; + $method = sub { + if ( $#_ ) { + return $original_method->($_[0], $coercion->($_[1])) + } + goto &$original_method; + } + } + + $method = $options{$_}->($method, $attribute, @_) + for sort keys %options; + + *{ _glob_for "${caller}::$attribute" } = $method; + + if ( $args{required} ) { + $metadata_for{$caller}{$attribute}{required} = 1; + } + + if ($args{clearer}) { + *{ _glob_for "${caller}::$args{clearer}" } + = sub { delete shift->{$attribute} } + } + + if ($args{predicate}) { + *{ _glob_for "${caller}::$args{predicate}" } + = sub { exists shift->{$attribute} } + } + + if ($args{handles}) { + _has_handles($caller, $attribute, \%args); + } + + if (exists $args{init_arg}) { + $metadata_for{$caller}{$attribute}{init_arg} = $args{init_arg}; + } + } + }, + %exports, + ); + + $export_for{$caller} = [ keys %exports ]; + + for my $keyword ( keys %exports ) { + *{ _glob_for "${caller}::$keyword" } = $exports{$keyword} + } + *{ _glob_for "${caller}::extends" }{CODE}->( "Mo::Object" ) + unless @{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] }; +}; + +sub _check_type_constaints { + my ($attribute, $I, $I_name, $val) = @_; + ( ref($I) eq 'CODE' + ? $I->($val) + : (ref $val eq $I + || ($val && $val eq $I) + || (exists $TYPES{$I} && $TYPES{$I}->($val))) + ) + || Carp::confess( + qq + . qq + . (defined $val ? Mo::Dumper($val) : 'undef') ) +} + +sub _has_handles { + my ($caller, $attribute, $args) = @_; + my $handles = $args->{handles}; + + my $ref = ref $handles; + my $kv; + if ( $ref eq ref [] ) { + $kv = { map { $_,$_ } @{$handles} }; + } + elsif ( $ref eq ref {} ) { + $kv = $handles; + } + elsif ( $ref eq ref qr// ) { + Carp::confess("Cannot delegate methods based on a Regexp without a type constraint (isa)") + unless $args->{isa}; + my $target_class = $args->{isa}; + $kv = { + map { $_, $_ } + grep { $_ =~ $handles } + grep { !exists $Mo::Object::{$_} && $target_class->can($_) } + grep { $_ ne 'has' && $_ ne 'extends' } + keys %{ _stash_for $target_class } + }; + } + else { + Carp::confess("handles for $ref not yet implemented"); + } + + while ( my ($method, $target) = each %{$kv} ) { + my $name = _glob_for "${caller}::$method"; + Carp::confess("You cannot overwrite a locally defined method ($method) with a delegation") + if defined &$name; + + my ($target, @curried_args) = ref($target) ? @$target : $target; + *$name = sub { + my $self = shift; + my $delegate_to = $self->$attribute(); + my $error = "Cannot delegate $method to $target because the value of $attribute"; + Carp::confess("$error is not defined") unless $delegate_to; + Carp::confess("$error is not an object (got '$delegate_to')") + unless Scalar::Util::blessed($delegate_to) || (!ref($delegate_to) && $delegate_to->can($target)); + return $delegate_to->$target(@curried_args, @_); + } + } +} + +sub _nested_constraints { + my ($attribute, $aggregate_type, $type) = @_; + + my $inner_types; + if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $inner_types = _nested_constraints($1, $2); + } + else { + $inner_types = $TYPES{$type}; + } + + if ( $aggregate_type eq 'ArrayRef' ) { + return sub { + my ($val) = @_; + return unless ref($val) eq ref([]); + + if ($inner_types) { + for my $value ( @{$val} ) { + return unless $inner_types->($value) + } + } + else { + for my $value ( @{$val} ) { + return unless $value && ($value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type))); + } + } + return 1; + }; + } + elsif ( $aggregate_type eq 'Maybe' ) { + return sub { + my ($value) = @_; + return 1 if ! defined($value); + if ($inner_types) { + return unless $inner_types->($value) + } + else { + return unless $value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type)); + } + return 1; + } + } + else { + Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); + } +} + +sub _set_package_isa { + my ($package, @new_isa) = @_; + + *{ _glob_for "${package}::ISA" } = [@new_isa]; +} + +sub _set_inherited_metadata { + my $class = shift; + my $linearized_isa = mro::get_linear_isa($class); + my %new_metadata; + + for my $isa_class (reverse @$linearized_isa) { + %new_metadata = ( + %new_metadata, + %{ $metadata_for{$isa_class} || {} }, + ); + } + $metadata_for{$class} = \%new_metadata; +} + +sub unimport { + my $caller = scalar caller(); + my $stash = _stash_for( $caller ); + + delete $stash->{$_} for @{$export_for{$caller}}; +} + +sub Dumper { + require Data::Dumper; + local $Data::Dumper::Indent = 0; + local $Data::Dumper::Sortkeys = 0; + local $Data::Dumper::Quotekeys = 0; + local $Data::Dumper::Terse = 1; + + Data::Dumper::Dumper(@_) +} + +BEGIN { + if ($] >= 5.010) { + { local $@; require mro; } + } + else { + local $@; + eval { + require MRO::Compat; + } or do { + *mro::get_linear_isa = *mro::get_linear_isa_dfs = sub { + no strict 'refs'; + + my $classname = shift; + + my @lin = ($classname); + my %stored; + foreach my $parent (@{"$classname\::ISA"}) { + my $plin = mro::get_linear_isa_dfs($parent); + foreach (@$plin) { + next if exists $stored{$_}; + push(@lin, $_); + $stored{$_} = 1; + } + } + return \@lin; + }; + } + } +} + +} +1; +} +# ########################################################################### +# End Mo package +# ########################################################################### + # ########################################################################### # DSNParser package # This package is a copy without comments from the original. The original @@ -2422,96 +2871,137 @@ sub deserialize_list { { package VersionParser; -use strict; -use warnings FATAL => 'all'; +use Mo; +use Scalar::Util qw(blessed); use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; -sub new { - my ( $class ) = @_; - bless {}, $class; -} +use overload ( + '""' => "version", + '<=>' => "cmp", + 'cmp' => "cmp", + fallback => 1, +); -sub parse { - my ( $self, $str ) = @_; - 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; -} +our $VERSION = 0.01; -sub version_cmp { - my ($self, $dbh, $target, $cmp) = @_; - my $version = $self->version($dbh); - my $result; +has major => ( + is => 'ro', + isa => 'Int', + required => 1, +); - 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" - } +has [qw( minor revision )] => ( + is => 'ro', + isa => 'Num', +); - PTDEBUG && _d($self->{$dbh}, $cmp, $target, ':', $result); - return $result; -} +has flavor => ( + is => 'ro', + isa => 'Str', + default => sub { 'Unknown' }, +); -sub version_ge { - my ( $self, $dbh, $target ) = @_; - return $self->version_cmp($dbh, $target, 'ge'); -} +has innodb_version => ( + is => 'ro', + isa => 'Str', + default => sub { 'NO' }, +); -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 series { + my $self = shift; + return $self->_join_version($self->major, $self->minor); } sub version { - my ( $self, $dbh ) = @_; - if ( !$self->{$dbh} ) { - $self->{$dbh} = $self->parse( - $dbh->selectrow_array('SELECT VERSION()')); - } - return $self->{$dbh}; + my $self = shift; + return $self->_join_version($self->major, $self->minor, $self->revision); } -sub innodb_version { +sub is_in { + my ($self, $target) = @_; + + return $self eq $target; +} + +sub _join_version { + my ($self, @parts) = @_; + + return join ".", map { my $c = $_; $c =~ s/^0\./0/; $c } grep defined, @parts; +} +sub _split_version { + my ($self, $str) = @_; + my @version_parts = map { s/^0(?=\d)/0./; $_ } $str =~ m/(\d+)/g; + return @version_parts[0..2]; +} + +sub normalized_version { + my ( $self ) = @_; + my @version_parts = map { $_ || 0 } $self->_split_version( $self->version ); + my $result = sprintf('%d%02d%02d', @version_parts); + PTDEBUG && _d($self->version, 'normalizes to', $result); + return $result; +} + +sub comment { + my ( $self, $cmd ) = @_; + my $v = $self->normalized_version(); + + return "/*!$v $cmd */" +} + +my @methods = qw(major minor revision); +sub cmp { + my ($left, $right) = @_; + my $right_obj = (blessed($right) && $right->isa(ref($left))) + ? $right + : ref($left)->new($right); + + my $retval = 0; + for my $m ( @methods ) { + last unless defined($left->$m) && defined($right_obj->$m); + $retval = $left->$m <=> $right_obj->$m; + last if $retval; + } + return $retval; +} + +sub BUILDARGS { + my $self = shift; + + if ( @_ == 1 ) { + my %args; + if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { + my $dbh = $_[0]; + my $query; + PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); + if ( eval { $query = $dbh->selectall_hashref(q) } ) { + @args{@methods} = $self->_split_version($query->{version}); + $args{flavor} = delete $query->{version_comment} + if $query->{version_comment}; + } + elsif ( eval { ($query) = $dbh->selectrow_array('SELECT VERSION()') } ) { + @args{@methods} = $self->_split_version($query); + } + else { + PTDEBUG && _d("Couldn't get the version from the dbh: $@"); + } + $args{innodb_version} = eval { $self->_innodb_version($dbh) }; + } + elsif ( !ref($_[0]) ) { + @args{@methods} = $self->_split_version($_[0]); + } + + for my $method (@methods) { + delete $args{$method} unless defined $args{$method}; + } + @_ = %args if %args; + } + + return $self->SUPER::BUILDARGS(@_); +} + +sub _innodb_version { my ( $self, $dbh ) = @_; return unless $dbh; my $innodb_version = "NO"; @@ -2549,6 +3039,7 @@ sub _d { print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; } +no Mo; 1; } # ########################################################################### @@ -2859,7 +3350,7 @@ sub get_keys { my ( $type, $cols ) = $key =~ m/(?:USING (\w+))? \((.+)\)/; my ( $special ) = $key =~ m/(FULLTEXT|SPATIAL)/; $type = $type || $special || 'BTREE'; - if ( $opts->{mysql_version} && $opts->{mysql_version} lt '004001000' + if ( $opts->{mysql_version} && $opts->{mysql_version} lt '4.1' && $engine =~ m/HEAP|MEMORY/i ) { $type = 'HASH'; # MySQL pre-4.1 supports only HASH indexes on HEAP @@ -3548,8 +4039,7 @@ sub main { my $master_server_id = $o->get('master-server-id'); if ( !$master_server_id ) { eval { - my $vp = new VersionParser(); - my $ms = new MasterSlave(VersionParser => $vp); + my $ms = new MasterSlave(); my $master_dsn = $ms->get_master_dsn($dbh, $dsn, $dp) or die "This server is not a slave"; my $master_dbh = $dp->get_dbh($dp->get_cxn_params($master_dsn), @@ -3876,8 +4366,7 @@ sub check_delay { # Collect a list of connections to the slaves. if ( $o->get('recurse') ) { PTDEBUG && _d('Recursing to slaves'); - my $vp = new VersionParser(); - my $ms = new MasterSlave(VersionParser => $vp); + my $ms = new MasterSlave(); $ms->recurse_to_slaves( { dbh => $dbh, dsn => $dsn, diff --git a/bin/pt-index-usage b/bin/pt-index-usage index aa5ce211..07a23fdf 100755 --- a/bin/pt-index-usage +++ b/bin/pt-index-usage @@ -1513,6 +1513,456 @@ if ( PTDEBUG ) { # End OptionParser package # ########################################################################### +# ########################################################################### +# Mo 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/Mo.pm +# t/lib/Mo.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +BEGIN { +$INC{"Mo.pm"} = __FILE__; +package Mo; +our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. + +{ + no strict 'refs'; + sub _glob_for { + return \*{shift()} + } + + sub _stash_for { + return \%{ shift() . "::" }; + } +} + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util (); + +our %TYPES = ( + Bool => sub { !$_[0] || (defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == 1) }, + Num => sub { defined $_[0] && &Scalar::Util::looks_like_number }, + Int => sub { defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == int $_[0] }, + Str => sub { defined $_[0] }, + Object => sub { defined $_[0] && &Scalar::Util::blessed }, + FileHandle => sub { local $@; require IO::Handle; fileno($_[0]) && $_[0]->opened }, + + map { + my $type = /R/ ? $_ : uc $_; + $_ . "Ref" => sub { ref $_[0] eq $type } + } qw(Array Code Hash Regexp Glob Scalar) +); + +our %metadata_for; +{ + package Mo::Object; + + sub new { + my $class = shift; + my $args = $class->BUILDARGS(@_); + + my @args_to_delete; + while ( my ($attr, $meta) = each %{$metadata_for{$class}} ) { + next unless exists $meta->{init_arg}; + my $init_arg = $meta->{init_arg}; + + if ( defined $init_arg ) { + $args->{$attr} = delete $args->{$init_arg}; + } + else { + push @args_to_delete, $attr; + } + } + + delete $args->{$_} for @args_to_delete; + + for my $attribute ( keys %$args ) { + if ( my $coerce = $metadata_for{$class}{$attribute}{coerce} ) { + $args->{$attribute} = $coerce->($args->{$attribute}); + } + if ( my $I = $metadata_for{$class}{$attribute}{isa} ) { + ( (my $I_name), $I ) = @{$I}; + Mo::_check_type_constaints($attribute, $I, $I_name, $args->{$attribute}); + } + } + + while ( my ($attribute, $meta) = each %{$metadata_for{$class}} ) { + next unless $meta->{required}; + Carp::confess("Attribute ($attribute) is required for $class") + if ! exists $args->{$attribute} + } + + @_ = %$args; + my $self = bless $args, $class; + + my @build_subs; + my $linearized_isa = mro::get_linear_isa($class); + + for my $isa_class ( @$linearized_isa ) { + unshift @build_subs, *{ Mo::_glob_for "${isa_class}::BUILD" }{CODE}; + } + exists &$_ && $_->( $self, @_ ) for grep { defined } @build_subs; + return $self; + } + + sub BUILDARGS { + shift; + my $ref; + if ( @_ == 1 && ref($_[0]) ) { + Carp::confess("Single parameters to new() must be a HASH ref") + unless ref($_[0]) eq ref({}); + $ref = {%{$_[0]}} # We want a new reference, always + } + else { + $ref = { @_ }; + } + return $ref; + } +} + +my %export_for; +sub Mo::import { + warnings->import(qw(FATAL all)); + strict->import(); + + my $caller = scalar caller(); # Caller's package + my $caller_pkg = $caller . "::"; # Caller's package with :: at the end + my (%exports, %options); + + my (undef, @features) = @_; + my %ignore = ( map { $_ => 1 } qw( is isa init_arg builder buildargs clearer predicate build handles default required ) ); + for my $feature (grep { !$ignore{$_} } @features) { + { local $@; require "Mo/$feature.pm"; } + { + no strict 'refs'; + &{"Mo::${feature}::e"}( + $caller_pkg, + \%exports, + \%options, + \@_ + ); + } + } + + return if $exports{M}; + + %exports = ( + extends => sub { + for my $class ( map { "$_" } @_ ) { + $class =~ s{::|'}{/}g; + { local $@; eval { require "$class.pm" } } # or warn $@; + } + _set_package_isa($caller, @_); + _set_inherited_metadata($caller); + }, + has => sub { + my $names = shift; + for my $attribute ( ref $names ? @$names : $names ) { + my %args = @_; + my $method = ($args{is} || '') eq 'ro' + ? sub { + Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller_pkg}${attribute}") + if $#_; + return $_[0]{$attribute}; + } + : sub { + return $#_ + ? $_[0]{$attribute} = $_[1] + : $_[0]{$attribute}; + }; + + $metadata_for{$caller}{$attribute} = (); + + if ( my $I = $args{isa} ) { + my $orig_I = $I; + my $type; + if ( $I =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $I = _nested_constraints($attribute, $1, $2); + } + $metadata_for{$caller}{$attribute}{isa} = [$orig_I, $I]; + my $orig_method = $method; + $method = sub { + if ( $#_ ) { + Mo::_check_type_constaints($attribute, $I, $orig_I, $_[1]); + } + goto &$orig_method; + }; + } + + if ( my $builder = $args{builder} ) { + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$builder + : goto &$original_method + }; + } + + if ( my $code = $args{default} ) { + Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") + unless ref($code) eq 'CODE'; + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$code + : goto &$original_method + }; + } + + if ( my $role = $args{does} ) { + my $original_method = $method; + $method = sub { + if ( $#_ ) { + Carp::confess(qq) + unless blessed($_[1]) && $_[1]->does($role) + } + goto &$original_method + }; + } + + if ( my $coercion = $args{coerce} ) { + $metadata_for{$caller}{$attribute}{coerce} = $coercion; + my $original_method = $method; + $method = sub { + if ( $#_ ) { + return $original_method->($_[0], $coercion->($_[1])) + } + goto &$original_method; + } + } + + $method = $options{$_}->($method, $attribute, @_) + for sort keys %options; + + *{ _glob_for "${caller}::$attribute" } = $method; + + if ( $args{required} ) { + $metadata_for{$caller}{$attribute}{required} = 1; + } + + if ($args{clearer}) { + *{ _glob_for "${caller}::$args{clearer}" } + = sub { delete shift->{$attribute} } + } + + if ($args{predicate}) { + *{ _glob_for "${caller}::$args{predicate}" } + = sub { exists shift->{$attribute} } + } + + if ($args{handles}) { + _has_handles($caller, $attribute, \%args); + } + + if (exists $args{init_arg}) { + $metadata_for{$caller}{$attribute}{init_arg} = $args{init_arg}; + } + } + }, + %exports, + ); + + $export_for{$caller} = [ keys %exports ]; + + for my $keyword ( keys %exports ) { + *{ _glob_for "${caller}::$keyword" } = $exports{$keyword} + } + *{ _glob_for "${caller}::extends" }{CODE}->( "Mo::Object" ) + unless @{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] }; +}; + +sub _check_type_constaints { + my ($attribute, $I, $I_name, $val) = @_; + ( ref($I) eq 'CODE' + ? $I->($val) + : (ref $val eq $I + || ($val && $val eq $I) + || (exists $TYPES{$I} && $TYPES{$I}->($val))) + ) + || Carp::confess( + qq + . qq + . (defined $val ? Mo::Dumper($val) : 'undef') ) +} + +sub _has_handles { + my ($caller, $attribute, $args) = @_; + my $handles = $args->{handles}; + + my $ref = ref $handles; + my $kv; + if ( $ref eq ref [] ) { + $kv = { map { $_,$_ } @{$handles} }; + } + elsif ( $ref eq ref {} ) { + $kv = $handles; + } + elsif ( $ref eq ref qr// ) { + Carp::confess("Cannot delegate methods based on a Regexp without a type constraint (isa)") + unless $args->{isa}; + my $target_class = $args->{isa}; + $kv = { + map { $_, $_ } + grep { $_ =~ $handles } + grep { !exists $Mo::Object::{$_} && $target_class->can($_) } + grep { $_ ne 'has' && $_ ne 'extends' } + keys %{ _stash_for $target_class } + }; + } + else { + Carp::confess("handles for $ref not yet implemented"); + } + + while ( my ($method, $target) = each %{$kv} ) { + my $name = _glob_for "${caller}::$method"; + Carp::confess("You cannot overwrite a locally defined method ($method) with a delegation") + if defined &$name; + + my ($target, @curried_args) = ref($target) ? @$target : $target; + *$name = sub { + my $self = shift; + my $delegate_to = $self->$attribute(); + my $error = "Cannot delegate $method to $target because the value of $attribute"; + Carp::confess("$error is not defined") unless $delegate_to; + Carp::confess("$error is not an object (got '$delegate_to')") + unless Scalar::Util::blessed($delegate_to) || (!ref($delegate_to) && $delegate_to->can($target)); + return $delegate_to->$target(@curried_args, @_); + } + } +} + +sub _nested_constraints { + my ($attribute, $aggregate_type, $type) = @_; + + my $inner_types; + if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $inner_types = _nested_constraints($1, $2); + } + else { + $inner_types = $TYPES{$type}; + } + + if ( $aggregate_type eq 'ArrayRef' ) { + return sub { + my ($val) = @_; + return unless ref($val) eq ref([]); + + if ($inner_types) { + for my $value ( @{$val} ) { + return unless $inner_types->($value) + } + } + else { + for my $value ( @{$val} ) { + return unless $value && ($value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type))); + } + } + return 1; + }; + } + elsif ( $aggregate_type eq 'Maybe' ) { + return sub { + my ($value) = @_; + return 1 if ! defined($value); + if ($inner_types) { + return unless $inner_types->($value) + } + else { + return unless $value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type)); + } + return 1; + } + } + else { + Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); + } +} + +sub _set_package_isa { + my ($package, @new_isa) = @_; + + *{ _glob_for "${package}::ISA" } = [@new_isa]; +} + +sub _set_inherited_metadata { + my $class = shift; + my $linearized_isa = mro::get_linear_isa($class); + my %new_metadata; + + for my $isa_class (reverse @$linearized_isa) { + %new_metadata = ( + %new_metadata, + %{ $metadata_for{$isa_class} || {} }, + ); + } + $metadata_for{$class} = \%new_metadata; +} + +sub unimport { + my $caller = scalar caller(); + my $stash = _stash_for( $caller ); + + delete $stash->{$_} for @{$export_for{$caller}}; +} + +sub Dumper { + require Data::Dumper; + local $Data::Dumper::Indent = 0; + local $Data::Dumper::Sortkeys = 0; + local $Data::Dumper::Quotekeys = 0; + local $Data::Dumper::Terse = 1; + + Data::Dumper::Dumper(@_) +} + +BEGIN { + if ($] >= 5.010) { + { local $@; require mro; } + } + else { + local $@; + eval { + require MRO::Compat; + } or do { + *mro::get_linear_isa = *mro::get_linear_isa_dfs = sub { + no strict 'refs'; + + my $classname = shift; + + my @lin = ($classname); + my %stored; + foreach my $parent (@{"$classname\::ISA"}) { + my $plin = mro::get_linear_isa_dfs($parent); + foreach (@$plin) { + next if exists $stored{$_}; + push(@lin, $_); + $stored{$_} = 1; + } + } + return \@lin; + }; + } + } +} + +} +1; +} +# ########################################################################### +# End Mo package +# ########################################################################### + # ########################################################################### # PodParser package # This package is a copy without comments from the original. The original @@ -3006,7 +3456,7 @@ sub get_keys { my ( $type, $cols ) = $key =~ m/(?:USING (\w+))? \((.+)\)/; my ( $special ) = $key =~ m/(FULLTEXT|SPATIAL)/; $type = $type || $special || 'BTREE'; - if ( $opts->{mysql_version} && $opts->{mysql_version} lt '004001000' + if ( $opts->{mysql_version} && $opts->{mysql_version} lt '4.1' && $engine =~ m/HEAP|MEMORY/i ) { $type = 'HASH'; # MySQL pre-4.1 supports only HASH indexes on HEAP @@ -3508,96 +3958,137 @@ sub _d { { package VersionParser; -use strict; -use warnings FATAL => 'all'; +use Mo; +use Scalar::Util qw(blessed); use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; -sub new { - my ( $class ) = @_; - bless {}, $class; -} +use overload ( + '""' => "version", + '<=>' => "cmp", + 'cmp' => "cmp", + fallback => 1, +); -sub parse { - my ( $self, $str ) = @_; - 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; -} +our $VERSION = 0.01; -sub version_cmp { - my ($self, $dbh, $target, $cmp) = @_; - my $version = $self->version($dbh); - my $result; +has major => ( + is => 'ro', + isa => 'Int', + required => 1, +); - 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" - } +has [qw( minor revision )] => ( + is => 'ro', + isa => 'Num', +); - PTDEBUG && _d($self->{$dbh}, $cmp, $target, ':', $result); - return $result; -} +has flavor => ( + is => 'ro', + isa => 'Str', + default => sub { 'Unknown' }, +); -sub version_ge { - my ( $self, $dbh, $target ) = @_; - return $self->version_cmp($dbh, $target, 'ge'); -} +has innodb_version => ( + is => 'ro', + isa => 'Str', + default => sub { 'NO' }, +); -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 series { + my $self = shift; + return $self->_join_version($self->major, $self->minor); } sub version { - my ( $self, $dbh ) = @_; - if ( !$self->{$dbh} ) { - $self->{$dbh} = $self->parse( - $dbh->selectrow_array('SELECT VERSION()')); - } - return $self->{$dbh}; + my $self = shift; + return $self->_join_version($self->major, $self->minor, $self->revision); } -sub innodb_version { +sub is_in { + my ($self, $target) = @_; + + return $self eq $target; +} + +sub _join_version { + my ($self, @parts) = @_; + + return join ".", map { my $c = $_; $c =~ s/^0\./0/; $c } grep defined, @parts; +} +sub _split_version { + my ($self, $str) = @_; + my @version_parts = map { s/^0(?=\d)/0./; $_ } $str =~ m/(\d+)/g; + return @version_parts[0..2]; +} + +sub normalized_version { + my ( $self ) = @_; + my @version_parts = map { $_ || 0 } $self->_split_version( $self->version ); + my $result = sprintf('%d%02d%02d', @version_parts); + PTDEBUG && _d($self->version, 'normalizes to', $result); + return $result; +} + +sub comment { + my ( $self, $cmd ) = @_; + my $v = $self->normalized_version(); + + return "/*!$v $cmd */" +} + +my @methods = qw(major minor revision); +sub cmp { + my ($left, $right) = @_; + my $right_obj = (blessed($right) && $right->isa(ref($left))) + ? $right + : ref($left)->new($right); + + my $retval = 0; + for my $m ( @methods ) { + last unless defined($left->$m) && defined($right_obj->$m); + $retval = $left->$m <=> $right_obj->$m; + last if $retval; + } + return $retval; +} + +sub BUILDARGS { + my $self = shift; + + if ( @_ == 1 ) { + my %args; + if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { + my $dbh = $_[0]; + my $query; + PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); + if ( eval { $query = $dbh->selectall_hashref(q) } ) { + @args{@methods} = $self->_split_version($query->{version}); + $args{flavor} = delete $query->{version_comment} + if $query->{version_comment}; + } + elsif ( eval { ($query) = $dbh->selectrow_array('SELECT VERSION()') } ) { + @args{@methods} = $self->_split_version($query); + } + else { + PTDEBUG && _d("Couldn't get the version from the dbh: $@"); + } + $args{innodb_version} = eval { $self->_innodb_version($dbh) }; + } + elsif ( !ref($_[0]) ) { + @args{@methods} = $self->_split_version($_[0]); + } + + for my $method (@methods) { + delete $args{$method} unless defined $args{$method}; + } + @_ = %args if %args; + } + + return $self->SUPER::BUILDARGS(@_); +} + +sub _innodb_version { my ( $self, $dbh ) = @_; return unless $dbh; my $innodb_version = "NO"; @@ -3635,6 +4126,7 @@ sub _d { print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; } +no Mo; 1; } # ########################################################################### @@ -5068,7 +5560,6 @@ sub main { my $qp = new QueryParser(); my $qr = new QueryRewriter(QueryParser => $qp); my $tp = new TableParser(Quoter => $q); - my $vp = new VersionParser(); my $parser = new SlowLogParser(); my $fi = new FileIterator(); my $iu = new IndexUsage( @@ -5085,7 +5576,6 @@ sub main { QueryParser => $qp, QueryRewriter => $qr, TableParser => $tp, - VersionParser => $vp, IndexUsage => $iu, ExplainAnalyzer => $exa, ); @@ -5152,7 +5642,7 @@ sub main { # guess which database to USE for EXPLAIN-ing it. This code block doesn't # read query logs, it's just inventorying the tables and indexes. # ######################################################################## - my $version = $vp->parse($dbh->selectrow_array('SELECT VERSION()')); + my $version = VersionParser->new($dbh); my $schema = new Schema(); my $schema_itr = new SchemaIterator( @@ -5169,7 +5659,7 @@ sub main { if ( $ddl =~ m/CREATE (?:TEMPORARY )?TABLE "/ ) { $ddl = $tp->ansi_to_legacy($ddl); } - my ($indexes) = $tp->get_keys($ddl, {version => $version}); + my ($indexes) = $tp->get_keys($ddl, {mysql_version => $version}); $iu->add_indexes(%$tbl, indexes=>$indexes); }; if ( $EVAL_ERROR ) { diff --git a/bin/pt-kill b/bin/pt-kill index 920a3ce4..d2aca332 100755 --- a/bin/pt-kill +++ b/bin/pt-kill @@ -1034,147 +1034,453 @@ if ( PTDEBUG ) { # ########################################################################### # ########################################################################### -# VersionParser package +# Mo 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/VersionParser.pm -# t/lib/VersionParser.t +# lib/Mo.pm +# t/lib/Mo.t # See https://launchpad.net/percona-toolkit for more information. # ########################################################################### { -package VersionParser; +BEGIN { +$INC{"Mo.pm"} = __FILE__; +package Mo; +our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. + +{ + no strict 'refs'; + sub _glob_for { + return \*{shift()} + } + + sub _stash_for { + return \%{ shift() . "::" }; + } +} use strict; -use warnings FATAL => 'all'; -use English qw(-no_match_vars); -use constant PTDEBUG => $ENV{PTDEBUG} || 0; +use warnings qw( FATAL all ); -sub new { - my ( $class ) = @_; - bless {}, $class; -} +use Carp (); +use Scalar::Util (); -sub parse { - my ( $self, $str ) = @_; - 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; -} +our %TYPES = ( + Bool => sub { !$_[0] || (defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == 1) }, + Num => sub { defined $_[0] && &Scalar::Util::looks_like_number }, + Int => sub { defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == int $_[0] }, + Str => sub { defined $_[0] }, + Object => sub { defined $_[0] && &Scalar::Util::blessed }, + FileHandle => sub { local $@; require IO::Handle; fileno($_[0]) && $_[0]->opened }, -sub version_cmp { - my ($self, $dbh, $target, $cmp) = @_; - my $version = $self->version($dbh); - my $result; + map { + my $type = /R/ ? $_ : uc $_; + $_ . "Ref" => sub { ref $_[0] eq $type } + } qw(Array Code Hash Regexp Glob Scalar) +); - 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" - } +our %metadata_for; +{ + package Mo::Object; - PTDEBUG && _d($self->{$dbh}, $cmp, $target, ':', $result); - return $result; -} + sub new { + my $class = shift; + my $args = $class->BUILDARGS(@_); -sub version_ge { - my ( $self, $dbh, $target ) = @_; - return $self->version_cmp($dbh, $target, 'ge'); -} + my @args_to_delete; + while ( my ($attr, $meta) = each %{$metadata_for{$class}} ) { + next unless exists $meta->{init_arg}; + my $init_arg = $meta->{init_arg}; -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()')); - } - return $self->{$dbh}; -} - -sub innodb_version { - my ( $self, $dbh ) = @_; - return unless $dbh; - my $innodb_version = "NO"; - - my ($innodb) = - grep { $_->{engine} =~ m/InnoDB/i } - map { - my %hash; - @hash{ map { lc $_ } keys %$_ } = values %$_; - \%hash; + if ( defined $init_arg ) { + $args->{$attr} = delete $args->{$init_arg}; + } + else { + push @args_to_delete, $attr; + } } - @{ $dbh->selectall_arrayref("SHOW ENGINES", {Slice=>{}}) }; - if ( $innodb ) { - PTDEBUG && _d("InnoDB support:", $innodb->{support}); - if ( $innodb->{support} =~ m/YES|DEFAULT/i ) { - my $vars = $dbh->selectrow_hashref( - "SHOW VARIABLES LIKE 'innodb_version'"); - $innodb_version = !$vars ? "BUILTIN" - : ($vars->{Value} || $vars->{value}); + + delete $args->{$_} for @args_to_delete; + + for my $attribute ( keys %$args ) { + if ( my $coerce = $metadata_for{$class}{$attribute}{coerce} ) { + $args->{$attribute} = $coerce->($args->{$attribute}); + } + if ( my $I = $metadata_for{$class}{$attribute}{isa} ) { + ( (my $I_name), $I ) = @{$I}; + Mo::_check_type_constaints($attribute, $I, $I_name, $args->{$attribute}); + } + } + + while ( my ($attribute, $meta) = each %{$metadata_for{$class}} ) { + next unless $meta->{required}; + Carp::confess("Attribute ($attribute) is required for $class") + if ! exists $args->{$attribute} + } + + @_ = %$args; + my $self = bless $args, $class; + + my @build_subs; + my $linearized_isa = mro::get_linear_isa($class); + + for my $isa_class ( @$linearized_isa ) { + unshift @build_subs, *{ Mo::_glob_for "${isa_class}::BUILD" }{CODE}; + } + exists &$_ && $_->( $self, @_ ) for grep { defined } @build_subs; + return $self; + } + + sub BUILDARGS { + shift; + my $ref; + if ( @_ == 1 && ref($_[0]) ) { + Carp::confess("Single parameters to new() must be a HASH ref") + unless ref($_[0]) eq ref({}); + $ref = {%{$_[0]}} # We want a new reference, always } else { - $innodb_version = $innodb->{support}; # probably DISABLED or NO + $ref = { @_ }; } + return $ref; + } +} + +my %export_for; +sub Mo::import { + warnings->import(qw(FATAL all)); + strict->import(); + + my $caller = scalar caller(); # Caller's package + my $caller_pkg = $caller . "::"; # Caller's package with :: at the end + my (%exports, %options); + + my (undef, @features) = @_; + my %ignore = ( map { $_ => 1 } qw( is isa init_arg builder buildargs clearer predicate build handles default required ) ); + for my $feature (grep { !$ignore{$_} } @features) { + { local $@; require "Mo/$feature.pm"; } + { + no strict 'refs'; + &{"Mo::${feature}::e"}( + $caller_pkg, + \%exports, + \%options, + \@_ + ); + } + } + + return if $exports{M}; + + %exports = ( + extends => sub { + for my $class ( map { "$_" } @_ ) { + $class =~ s{::|'}{/}g; + { local $@; eval { require "$class.pm" } } # or warn $@; + } + _set_package_isa($caller, @_); + _set_inherited_metadata($caller); + }, + has => sub { + my $names = shift; + for my $attribute ( ref $names ? @$names : $names ) { + my %args = @_; + my $method = ($args{is} || '') eq 'ro' + ? sub { + Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller_pkg}${attribute}") + if $#_; + return $_[0]{$attribute}; + } + : sub { + return $#_ + ? $_[0]{$attribute} = $_[1] + : $_[0]{$attribute}; + }; + + $metadata_for{$caller}{$attribute} = (); + + if ( my $I = $args{isa} ) { + my $orig_I = $I; + my $type; + if ( $I =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $I = _nested_constraints($attribute, $1, $2); + } + $metadata_for{$caller}{$attribute}{isa} = [$orig_I, $I]; + my $orig_method = $method; + $method = sub { + if ( $#_ ) { + Mo::_check_type_constaints($attribute, $I, $orig_I, $_[1]); + } + goto &$orig_method; + }; + } + + if ( my $builder = $args{builder} ) { + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$builder + : goto &$original_method + }; + } + + if ( my $code = $args{default} ) { + Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") + unless ref($code) eq 'CODE'; + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$code + : goto &$original_method + }; + } + + if ( my $role = $args{does} ) { + my $original_method = $method; + $method = sub { + if ( $#_ ) { + Carp::confess(qq) + unless blessed($_[1]) && $_[1]->does($role) + } + goto &$original_method + }; + } + + if ( my $coercion = $args{coerce} ) { + $metadata_for{$caller}{$attribute}{coerce} = $coercion; + my $original_method = $method; + $method = sub { + if ( $#_ ) { + return $original_method->($_[0], $coercion->($_[1])) + } + goto &$original_method; + } + } + + $method = $options{$_}->($method, $attribute, @_) + for sort keys %options; + + *{ _glob_for "${caller}::$attribute" } = $method; + + if ( $args{required} ) { + $metadata_for{$caller}{$attribute}{required} = 1; + } + + if ($args{clearer}) { + *{ _glob_for "${caller}::$args{clearer}" } + = sub { delete shift->{$attribute} } + } + + if ($args{predicate}) { + *{ _glob_for "${caller}::$args{predicate}" } + = sub { exists shift->{$attribute} } + } + + if ($args{handles}) { + _has_handles($caller, $attribute, \%args); + } + + if (exists $args{init_arg}) { + $metadata_for{$caller}{$attribute}{init_arg} = $args{init_arg}; + } + } + }, + %exports, + ); + + $export_for{$caller} = [ keys %exports ]; + + for my $keyword ( keys %exports ) { + *{ _glob_for "${caller}::$keyword" } = $exports{$keyword} + } + *{ _glob_for "${caller}::extends" }{CODE}->( "Mo::Object" ) + unless @{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] }; +}; + +sub _check_type_constaints { + my ($attribute, $I, $I_name, $val) = @_; + ( ref($I) eq 'CODE' + ? $I->($val) + : (ref $val eq $I + || ($val && $val eq $I) + || (exists $TYPES{$I} && $TYPES{$I}->($val))) + ) + || Carp::confess( + qq + . qq + . (defined $val ? Mo::Dumper($val) : 'undef') ) +} + +sub _has_handles { + my ($caller, $attribute, $args) = @_; + my $handles = $args->{handles}; + + my $ref = ref $handles; + my $kv; + if ( $ref eq ref [] ) { + $kv = { map { $_,$_ } @{$handles} }; + } + elsif ( $ref eq ref {} ) { + $kv = $handles; + } + elsif ( $ref eq ref qr// ) { + Carp::confess("Cannot delegate methods based on a Regexp without a type constraint (isa)") + unless $args->{isa}; + my $target_class = $args->{isa}; + $kv = { + map { $_, $_ } + grep { $_ =~ $handles } + grep { !exists $Mo::Object::{$_} && $target_class->can($_) } + grep { $_ ne 'has' && $_ ne 'extends' } + keys %{ _stash_for $target_class } + }; + } + else { + Carp::confess("handles for $ref not yet implemented"); } - PTDEBUG && _d("InnoDB version:", $innodb_version); - return $innodb_version; + while ( my ($method, $target) = each %{$kv} ) { + my $name = _glob_for "${caller}::$method"; + Carp::confess("You cannot overwrite a locally defined method ($method) with a delegation") + if defined &$name; + + my ($target, @curried_args) = ref($target) ? @$target : $target; + *$name = sub { + my $self = shift; + my $delegate_to = $self->$attribute(); + my $error = "Cannot delegate $method to $target because the value of $attribute"; + Carp::confess("$error is not defined") unless $delegate_to; + Carp::confess("$error is not an object (got '$delegate_to')") + unless Scalar::Util::blessed($delegate_to) || (!ref($delegate_to) && $delegate_to->can($target)); + return $delegate_to->$target(@curried_args, @_); + } + } } -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"; +sub _nested_constraints { + my ($attribute, $aggregate_type, $type) = @_; + + my $inner_types; + if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $inner_types = _nested_constraints($1, $2); + } + else { + $inner_types = $TYPES{$type}; + } + + if ( $aggregate_type eq 'ArrayRef' ) { + return sub { + my ($val) = @_; + return unless ref($val) eq ref([]); + + if ($inner_types) { + for my $value ( @{$val} ) { + return unless $inner_types->($value) + } + } + else { + for my $value ( @{$val} ) { + return unless $value && ($value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type))); + } + } + return 1; + }; + } + elsif ( $aggregate_type eq 'Maybe' ) { + return sub { + my ($value) = @_; + return 1 if ! defined($value); + if ($inner_types) { + return unless $inner_types->($value) + } + else { + return unless $value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type)); + } + return 1; + } + } + else { + Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); + } } +sub _set_package_isa { + my ($package, @new_isa) = @_; + + *{ _glob_for "${package}::ISA" } = [@new_isa]; +} + +sub _set_inherited_metadata { + my $class = shift; + my $linearized_isa = mro::get_linear_isa($class); + my %new_metadata; + + for my $isa_class (reverse @$linearized_isa) { + %new_metadata = ( + %new_metadata, + %{ $metadata_for{$isa_class} || {} }, + ); + } + $metadata_for{$class} = \%new_metadata; +} + +sub unimport { + my $caller = scalar caller(); + my $stash = _stash_for( $caller ); + + delete $stash->{$_} for @{$export_for{$caller}}; +} + +sub Dumper { + require Data::Dumper; + local $Data::Dumper::Indent = 0; + local $Data::Dumper::Sortkeys = 0; + local $Data::Dumper::Quotekeys = 0; + local $Data::Dumper::Terse = 1; + + Data::Dumper::Dumper(@_) +} + +BEGIN { + if ($] >= 5.010) { + { local $@; require mro; } + } + else { + local $@; + eval { + require MRO::Compat; + } or do { + *mro::get_linear_isa = *mro::get_linear_isa_dfs = sub { + no strict 'refs'; + + my $classname = shift; + + my @lin = ($classname); + my %stored; + foreach my $parent (@{"$classname\::ISA"}) { + my $plin = mro::get_linear_isa_dfs($parent); + foreach (@$plin) { + next if exists $stored{$_}; + push(@lin, $_); + $stored{$_} = 1; + } + } + return \@lin; + }; + } + } +} + +} 1; } # ########################################################################### -# End VersionParser package +# End Mo package # ########################################################################### # ########################################################################### @@ -2790,8 +3096,7 @@ sub get_connected_slaves { my $show = "SHOW GRANTS FOR "; my $user = 'CURRENT_USER()'; - my $vp = $self->{VersionParser}; - if ( $vp && !$vp->version_ge($dbh, '4.1.2') ) { + if ( VersionParser->new($dbh) < '4.1.2' ) { $user = $dbh->selectrow_arrayref('SELECT USER()')->[0]; $user =~ s/([^@]+)@(.+)/'$1'\@'$2'/; } @@ -3711,7 +4016,6 @@ sub main { my $ms = new MasterSlave(); my $pl = new Processlist(MasterSlave => $ms); - my $vp = new VersionParser(); my $qr = new QueryRewriter(); # ######################################################################## diff --git a/bin/pt-online-schema-change b/bin/pt-online-schema-change index e450a65d..94ce1329 100755 --- a/bin/pt-online-schema-change +++ b/bin/pt-online-schema-change @@ -1033,6 +1033,456 @@ if ( PTDEBUG ) { # End OptionParser package # ########################################################################### +# ########################################################################### +# Mo 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/Mo.pm +# t/lib/Mo.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +BEGIN { +$INC{"Mo.pm"} = __FILE__; +package Mo; +our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. + +{ + no strict 'refs'; + sub _glob_for { + return \*{shift()} + } + + sub _stash_for { + return \%{ shift() . "::" }; + } +} + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util (); + +our %TYPES = ( + Bool => sub { !$_[0] || (defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == 1) }, + Num => sub { defined $_[0] && &Scalar::Util::looks_like_number }, + Int => sub { defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == int $_[0] }, + Str => sub { defined $_[0] }, + Object => sub { defined $_[0] && &Scalar::Util::blessed }, + FileHandle => sub { local $@; require IO::Handle; fileno($_[0]) && $_[0]->opened }, + + map { + my $type = /R/ ? $_ : uc $_; + $_ . "Ref" => sub { ref $_[0] eq $type } + } qw(Array Code Hash Regexp Glob Scalar) +); + +our %metadata_for; +{ + package Mo::Object; + + sub new { + my $class = shift; + my $args = $class->BUILDARGS(@_); + + my @args_to_delete; + while ( my ($attr, $meta) = each %{$metadata_for{$class}} ) { + next unless exists $meta->{init_arg}; + my $init_arg = $meta->{init_arg}; + + if ( defined $init_arg ) { + $args->{$attr} = delete $args->{$init_arg}; + } + else { + push @args_to_delete, $attr; + } + } + + delete $args->{$_} for @args_to_delete; + + for my $attribute ( keys %$args ) { + if ( my $coerce = $metadata_for{$class}{$attribute}{coerce} ) { + $args->{$attribute} = $coerce->($args->{$attribute}); + } + if ( my $I = $metadata_for{$class}{$attribute}{isa} ) { + ( (my $I_name), $I ) = @{$I}; + Mo::_check_type_constaints($attribute, $I, $I_name, $args->{$attribute}); + } + } + + while ( my ($attribute, $meta) = each %{$metadata_for{$class}} ) { + next unless $meta->{required}; + Carp::confess("Attribute ($attribute) is required for $class") + if ! exists $args->{$attribute} + } + + @_ = %$args; + my $self = bless $args, $class; + + my @build_subs; + my $linearized_isa = mro::get_linear_isa($class); + + for my $isa_class ( @$linearized_isa ) { + unshift @build_subs, *{ Mo::_glob_for "${isa_class}::BUILD" }{CODE}; + } + exists &$_ && $_->( $self, @_ ) for grep { defined } @build_subs; + return $self; + } + + sub BUILDARGS { + shift; + my $ref; + if ( @_ == 1 && ref($_[0]) ) { + Carp::confess("Single parameters to new() must be a HASH ref") + unless ref($_[0]) eq ref({}); + $ref = {%{$_[0]}} # We want a new reference, always + } + else { + $ref = { @_ }; + } + return $ref; + } +} + +my %export_for; +sub Mo::import { + warnings->import(qw(FATAL all)); + strict->import(); + + my $caller = scalar caller(); # Caller's package + my $caller_pkg = $caller . "::"; # Caller's package with :: at the end + my (%exports, %options); + + my (undef, @features) = @_; + my %ignore = ( map { $_ => 1 } qw( is isa init_arg builder buildargs clearer predicate build handles default required ) ); + for my $feature (grep { !$ignore{$_} } @features) { + { local $@; require "Mo/$feature.pm"; } + { + no strict 'refs'; + &{"Mo::${feature}::e"}( + $caller_pkg, + \%exports, + \%options, + \@_ + ); + } + } + + return if $exports{M}; + + %exports = ( + extends => sub { + for my $class ( map { "$_" } @_ ) { + $class =~ s{::|'}{/}g; + { local $@; eval { require "$class.pm" } } # or warn $@; + } + _set_package_isa($caller, @_); + _set_inherited_metadata($caller); + }, + has => sub { + my $names = shift; + for my $attribute ( ref $names ? @$names : $names ) { + my %args = @_; + my $method = ($args{is} || '') eq 'ro' + ? sub { + Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller_pkg}${attribute}") + if $#_; + return $_[0]{$attribute}; + } + : sub { + return $#_ + ? $_[0]{$attribute} = $_[1] + : $_[0]{$attribute}; + }; + + $metadata_for{$caller}{$attribute} = (); + + if ( my $I = $args{isa} ) { + my $orig_I = $I; + my $type; + if ( $I =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $I = _nested_constraints($attribute, $1, $2); + } + $metadata_for{$caller}{$attribute}{isa} = [$orig_I, $I]; + my $orig_method = $method; + $method = sub { + if ( $#_ ) { + Mo::_check_type_constaints($attribute, $I, $orig_I, $_[1]); + } + goto &$orig_method; + }; + } + + if ( my $builder = $args{builder} ) { + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$builder + : goto &$original_method + }; + } + + if ( my $code = $args{default} ) { + Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") + unless ref($code) eq 'CODE'; + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$code + : goto &$original_method + }; + } + + if ( my $role = $args{does} ) { + my $original_method = $method; + $method = sub { + if ( $#_ ) { + Carp::confess(qq) + unless blessed($_[1]) && $_[1]->does($role) + } + goto &$original_method + }; + } + + if ( my $coercion = $args{coerce} ) { + $metadata_for{$caller}{$attribute}{coerce} = $coercion; + my $original_method = $method; + $method = sub { + if ( $#_ ) { + return $original_method->($_[0], $coercion->($_[1])) + } + goto &$original_method; + } + } + + $method = $options{$_}->($method, $attribute, @_) + for sort keys %options; + + *{ _glob_for "${caller}::$attribute" } = $method; + + if ( $args{required} ) { + $metadata_for{$caller}{$attribute}{required} = 1; + } + + if ($args{clearer}) { + *{ _glob_for "${caller}::$args{clearer}" } + = sub { delete shift->{$attribute} } + } + + if ($args{predicate}) { + *{ _glob_for "${caller}::$args{predicate}" } + = sub { exists shift->{$attribute} } + } + + if ($args{handles}) { + _has_handles($caller, $attribute, \%args); + } + + if (exists $args{init_arg}) { + $metadata_for{$caller}{$attribute}{init_arg} = $args{init_arg}; + } + } + }, + %exports, + ); + + $export_for{$caller} = [ keys %exports ]; + + for my $keyword ( keys %exports ) { + *{ _glob_for "${caller}::$keyword" } = $exports{$keyword} + } + *{ _glob_for "${caller}::extends" }{CODE}->( "Mo::Object" ) + unless @{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] }; +}; + +sub _check_type_constaints { + my ($attribute, $I, $I_name, $val) = @_; + ( ref($I) eq 'CODE' + ? $I->($val) + : (ref $val eq $I + || ($val && $val eq $I) + || (exists $TYPES{$I} && $TYPES{$I}->($val))) + ) + || Carp::confess( + qq + . qq + . (defined $val ? Mo::Dumper($val) : 'undef') ) +} + +sub _has_handles { + my ($caller, $attribute, $args) = @_; + my $handles = $args->{handles}; + + my $ref = ref $handles; + my $kv; + if ( $ref eq ref [] ) { + $kv = { map { $_,$_ } @{$handles} }; + } + elsif ( $ref eq ref {} ) { + $kv = $handles; + } + elsif ( $ref eq ref qr// ) { + Carp::confess("Cannot delegate methods based on a Regexp without a type constraint (isa)") + unless $args->{isa}; + my $target_class = $args->{isa}; + $kv = { + map { $_, $_ } + grep { $_ =~ $handles } + grep { !exists $Mo::Object::{$_} && $target_class->can($_) } + grep { $_ ne 'has' && $_ ne 'extends' } + keys %{ _stash_for $target_class } + }; + } + else { + Carp::confess("handles for $ref not yet implemented"); + } + + while ( my ($method, $target) = each %{$kv} ) { + my $name = _glob_for "${caller}::$method"; + Carp::confess("You cannot overwrite a locally defined method ($method) with a delegation") + if defined &$name; + + my ($target, @curried_args) = ref($target) ? @$target : $target; + *$name = sub { + my $self = shift; + my $delegate_to = $self->$attribute(); + my $error = "Cannot delegate $method to $target because the value of $attribute"; + Carp::confess("$error is not defined") unless $delegate_to; + Carp::confess("$error is not an object (got '$delegate_to')") + unless Scalar::Util::blessed($delegate_to) || (!ref($delegate_to) && $delegate_to->can($target)); + return $delegate_to->$target(@curried_args, @_); + } + } +} + +sub _nested_constraints { + my ($attribute, $aggregate_type, $type) = @_; + + my $inner_types; + if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $inner_types = _nested_constraints($1, $2); + } + else { + $inner_types = $TYPES{$type}; + } + + if ( $aggregate_type eq 'ArrayRef' ) { + return sub { + my ($val) = @_; + return unless ref($val) eq ref([]); + + if ($inner_types) { + for my $value ( @{$val} ) { + return unless $inner_types->($value) + } + } + else { + for my $value ( @{$val} ) { + return unless $value && ($value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type))); + } + } + return 1; + }; + } + elsif ( $aggregate_type eq 'Maybe' ) { + return sub { + my ($value) = @_; + return 1 if ! defined($value); + if ($inner_types) { + return unless $inner_types->($value) + } + else { + return unless $value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type)); + } + return 1; + } + } + else { + Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); + } +} + +sub _set_package_isa { + my ($package, @new_isa) = @_; + + *{ _glob_for "${package}::ISA" } = [@new_isa]; +} + +sub _set_inherited_metadata { + my $class = shift; + my $linearized_isa = mro::get_linear_isa($class); + my %new_metadata; + + for my $isa_class (reverse @$linearized_isa) { + %new_metadata = ( + %new_metadata, + %{ $metadata_for{$isa_class} || {} }, + ); + } + $metadata_for{$class} = \%new_metadata; +} + +sub unimport { + my $caller = scalar caller(); + my $stash = _stash_for( $caller ); + + delete $stash->{$_} for @{$export_for{$caller}}; +} + +sub Dumper { + require Data::Dumper; + local $Data::Dumper::Indent = 0; + local $Data::Dumper::Sortkeys = 0; + local $Data::Dumper::Quotekeys = 0; + local $Data::Dumper::Terse = 1; + + Data::Dumper::Dumper(@_) +} + +BEGIN { + if ($] >= 5.010) { + { local $@; require mro; } + } + else { + local $@; + eval { + require MRO::Compat; + } or do { + *mro::get_linear_isa = *mro::get_linear_isa_dfs = sub { + no strict 'refs'; + + my $classname = shift; + + my @lin = ($classname); + my %stored; + foreach my $parent (@{"$classname\::ISA"}) { + my $plin = mro::get_linear_isa_dfs($parent); + foreach (@$plin) { + next if exists $stored{$_}; + push(@lin, $_); + $stored{$_} = 1; + } + } + return \@lin; + }; + } + } +} + +} +1; +} +# ########################################################################### +# End Mo package +# ########################################################################### + # ########################################################################### # VersionParser package # This package is a copy without comments from the original. The original @@ -1044,96 +1494,137 @@ if ( PTDEBUG ) { { package VersionParser; -use strict; -use warnings FATAL => 'all'; +use Mo; +use Scalar::Util qw(blessed); use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; -sub new { - my ( $class ) = @_; - bless {}, $class; -} +use overload ( + '""' => "version", + '<=>' => "cmp", + 'cmp' => "cmp", + fallback => 1, +); -sub parse { - my ( $self, $str ) = @_; - 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; -} +our $VERSION = 0.01; -sub version_cmp { - my ($self, $dbh, $target, $cmp) = @_; - my $version = $self->version($dbh); - my $result; +has major => ( + is => 'ro', + isa => 'Int', + required => 1, +); - 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" - } +has [qw( minor revision )] => ( + is => 'ro', + isa => 'Num', +); - PTDEBUG && _d($self->{$dbh}, $cmp, $target, ':', $result); - return $result; -} +has flavor => ( + is => 'ro', + isa => 'Str', + default => sub { 'Unknown' }, +); -sub version_ge { - my ( $self, $dbh, $target ) = @_; - return $self->version_cmp($dbh, $target, 'ge'); -} +has innodb_version => ( + is => 'ro', + isa => 'Str', + default => sub { 'NO' }, +); -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 series { + my $self = shift; + return $self->_join_version($self->major, $self->minor); } sub version { - my ( $self, $dbh ) = @_; - if ( !$self->{$dbh} ) { - $self->{$dbh} = $self->parse( - $dbh->selectrow_array('SELECT VERSION()')); - } - return $self->{$dbh}; + my $self = shift; + return $self->_join_version($self->major, $self->minor, $self->revision); } -sub innodb_version { +sub is_in { + my ($self, $target) = @_; + + return $self eq $target; +} + +sub _join_version { + my ($self, @parts) = @_; + + return join ".", map { my $c = $_; $c =~ s/^0\./0/; $c } grep defined, @parts; +} +sub _split_version { + my ($self, $str) = @_; + my @version_parts = map { s/^0(?=\d)/0./; $_ } $str =~ m/(\d+)/g; + return @version_parts[0..2]; +} + +sub normalized_version { + my ( $self ) = @_; + my @version_parts = map { $_ || 0 } $self->_split_version( $self->version ); + my $result = sprintf('%d%02d%02d', @version_parts); + PTDEBUG && _d($self->version, 'normalizes to', $result); + return $result; +} + +sub comment { + my ( $self, $cmd ) = @_; + my $v = $self->normalized_version(); + + return "/*!$v $cmd */" +} + +my @methods = qw(major minor revision); +sub cmp { + my ($left, $right) = @_; + my $right_obj = (blessed($right) && $right->isa(ref($left))) + ? $right + : ref($left)->new($right); + + my $retval = 0; + for my $m ( @methods ) { + last unless defined($left->$m) && defined($right_obj->$m); + $retval = $left->$m <=> $right_obj->$m; + last if $retval; + } + return $retval; +} + +sub BUILDARGS { + my $self = shift; + + if ( @_ == 1 ) { + my %args; + if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { + my $dbh = $_[0]; + my $query; + PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); + if ( eval { $query = $dbh->selectall_hashref(q) } ) { + @args{@methods} = $self->_split_version($query->{version}); + $args{flavor} = delete $query->{version_comment} + if $query->{version_comment}; + } + elsif ( eval { ($query) = $dbh->selectrow_array('SELECT VERSION()') } ) { + @args{@methods} = $self->_split_version($query); + } + else { + PTDEBUG && _d("Couldn't get the version from the dbh: $@"); + } + $args{innodb_version} = eval { $self->_innodb_version($dbh) }; + } + elsif ( !ref($_[0]) ) { + @args{@methods} = $self->_split_version($_[0]); + } + + for my $method (@methods) { + delete $args{$method} unless defined $args{$method}; + } + @_ = %args if %args; + } + + return $self->SUPER::BUILDARGS(@_); +} + +sub _innodb_version { my ( $self, $dbh ) = @_; return unless $dbh; my $innodb_version = "NO"; @@ -1171,6 +1662,7 @@ sub _d { print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; } +no Mo; 1; } # ########################################################################### @@ -2413,7 +2905,7 @@ sub get_keys { my ( $type, $cols ) = $key =~ m/(?:USING (\w+))? \((.+)\)/; my ( $special ) = $key =~ m/(FULLTEXT|SPATIAL)/; $type = $type || $special || 'BTREE'; - if ( $opts->{mysql_version} && $opts->{mysql_version} lt '004001000' + if ( $opts->{mysql_version} && $opts->{mysql_version} lt '4.1' && $engine =~ m/HEAP|MEMORY/i ) { $type = 'HASH'; # MySQL pre-4.1 supports only HASH indexes on HEAP @@ -3140,8 +3632,7 @@ sub get_connected_slaves { my $show = "SHOW GRANTS FOR "; my $user = 'CURRENT_USER()'; - my $vp = $self->{VersionParser}; - if ( $vp && !$vp->version_ge($dbh, '4.1.2') ) { + if ( VersionParser->new($dbh) < '4.1.2' ) { $user = $dbh->selectrow_arrayref('SELECT USER()')->[0]; $user =~ s/([^@]+)@(.+)/'$1'\@'$2'/; } @@ -5484,8 +5975,7 @@ sub main { # Although triggers were introduced in 5.0.2, "Prior to MySQL 5.0.10, # triggers cannot contain direct references to tables by name." # ######################################################################## - my $vp = new VersionParser(); - if ( !$vp->version_ge($cxn->dbh(), '5.0.10') ) { + if ( VersionParser->new($cxn->dbh()) < '5.0.10' ) { die "This tool requires MySQL 5.0.10 or newer.\n"; } diff --git a/bin/pt-slave-delay b/bin/pt-slave-delay index 8ebe47f6..ca3280dd 100755 --- a/bin/pt-slave-delay +++ b/bin/pt-slave-delay @@ -1034,147 +1034,453 @@ if ( PTDEBUG ) { # ########################################################################### # ########################################################################### -# VersionParser package +# Mo 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/VersionParser.pm -# t/lib/VersionParser.t +# lib/Mo.pm +# t/lib/Mo.t # See https://launchpad.net/percona-toolkit for more information. # ########################################################################### { -package VersionParser; +BEGIN { +$INC{"Mo.pm"} = __FILE__; +package Mo; +our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. + +{ + no strict 'refs'; + sub _glob_for { + return \*{shift()} + } + + sub _stash_for { + return \%{ shift() . "::" }; + } +} use strict; -use warnings FATAL => 'all'; -use English qw(-no_match_vars); -use constant PTDEBUG => $ENV{PTDEBUG} || 0; +use warnings qw( FATAL all ); -sub new { - my ( $class ) = @_; - bless {}, $class; -} +use Carp (); +use Scalar::Util (); -sub parse { - my ( $self, $str ) = @_; - 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; -} +our %TYPES = ( + Bool => sub { !$_[0] || (defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == 1) }, + Num => sub { defined $_[0] && &Scalar::Util::looks_like_number }, + Int => sub { defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == int $_[0] }, + Str => sub { defined $_[0] }, + Object => sub { defined $_[0] && &Scalar::Util::blessed }, + FileHandle => sub { local $@; require IO::Handle; fileno($_[0]) && $_[0]->opened }, -sub version_cmp { - my ($self, $dbh, $target, $cmp) = @_; - my $version = $self->version($dbh); - my $result; + map { + my $type = /R/ ? $_ : uc $_; + $_ . "Ref" => sub { ref $_[0] eq $type } + } qw(Array Code Hash Regexp Glob Scalar) +); - 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" - } +our %metadata_for; +{ + package Mo::Object; - PTDEBUG && _d($self->{$dbh}, $cmp, $target, ':', $result); - return $result; -} + sub new { + my $class = shift; + my $args = $class->BUILDARGS(@_); -sub version_ge { - my ( $self, $dbh, $target ) = @_; - return $self->version_cmp($dbh, $target, 'ge'); -} + my @args_to_delete; + while ( my ($attr, $meta) = each %{$metadata_for{$class}} ) { + next unless exists $meta->{init_arg}; + my $init_arg = $meta->{init_arg}; -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()')); - } - return $self->{$dbh}; -} - -sub innodb_version { - my ( $self, $dbh ) = @_; - return unless $dbh; - my $innodb_version = "NO"; - - my ($innodb) = - grep { $_->{engine} =~ m/InnoDB/i } - map { - my %hash; - @hash{ map { lc $_ } keys %$_ } = values %$_; - \%hash; + if ( defined $init_arg ) { + $args->{$attr} = delete $args->{$init_arg}; + } + else { + push @args_to_delete, $attr; + } } - @{ $dbh->selectall_arrayref("SHOW ENGINES", {Slice=>{}}) }; - if ( $innodb ) { - PTDEBUG && _d("InnoDB support:", $innodb->{support}); - if ( $innodb->{support} =~ m/YES|DEFAULT/i ) { - my $vars = $dbh->selectrow_hashref( - "SHOW VARIABLES LIKE 'innodb_version'"); - $innodb_version = !$vars ? "BUILTIN" - : ($vars->{Value} || $vars->{value}); + + delete $args->{$_} for @args_to_delete; + + for my $attribute ( keys %$args ) { + if ( my $coerce = $metadata_for{$class}{$attribute}{coerce} ) { + $args->{$attribute} = $coerce->($args->{$attribute}); + } + if ( my $I = $metadata_for{$class}{$attribute}{isa} ) { + ( (my $I_name), $I ) = @{$I}; + Mo::_check_type_constaints($attribute, $I, $I_name, $args->{$attribute}); + } + } + + while ( my ($attribute, $meta) = each %{$metadata_for{$class}} ) { + next unless $meta->{required}; + Carp::confess("Attribute ($attribute) is required for $class") + if ! exists $args->{$attribute} + } + + @_ = %$args; + my $self = bless $args, $class; + + my @build_subs; + my $linearized_isa = mro::get_linear_isa($class); + + for my $isa_class ( @$linearized_isa ) { + unshift @build_subs, *{ Mo::_glob_for "${isa_class}::BUILD" }{CODE}; + } + exists &$_ && $_->( $self, @_ ) for grep { defined } @build_subs; + return $self; + } + + sub BUILDARGS { + shift; + my $ref; + if ( @_ == 1 && ref($_[0]) ) { + Carp::confess("Single parameters to new() must be a HASH ref") + unless ref($_[0]) eq ref({}); + $ref = {%{$_[0]}} # We want a new reference, always } else { - $innodb_version = $innodb->{support}; # probably DISABLED or NO + $ref = { @_ }; } + return $ref; + } +} + +my %export_for; +sub Mo::import { + warnings->import(qw(FATAL all)); + strict->import(); + + my $caller = scalar caller(); # Caller's package + my $caller_pkg = $caller . "::"; # Caller's package with :: at the end + my (%exports, %options); + + my (undef, @features) = @_; + my %ignore = ( map { $_ => 1 } qw( is isa init_arg builder buildargs clearer predicate build handles default required ) ); + for my $feature (grep { !$ignore{$_} } @features) { + { local $@; require "Mo/$feature.pm"; } + { + no strict 'refs'; + &{"Mo::${feature}::e"}( + $caller_pkg, + \%exports, + \%options, + \@_ + ); + } + } + + return if $exports{M}; + + %exports = ( + extends => sub { + for my $class ( map { "$_" } @_ ) { + $class =~ s{::|'}{/}g; + { local $@; eval { require "$class.pm" } } # or warn $@; + } + _set_package_isa($caller, @_); + _set_inherited_metadata($caller); + }, + has => sub { + my $names = shift; + for my $attribute ( ref $names ? @$names : $names ) { + my %args = @_; + my $method = ($args{is} || '') eq 'ro' + ? sub { + Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller_pkg}${attribute}") + if $#_; + return $_[0]{$attribute}; + } + : sub { + return $#_ + ? $_[0]{$attribute} = $_[1] + : $_[0]{$attribute}; + }; + + $metadata_for{$caller}{$attribute} = (); + + if ( my $I = $args{isa} ) { + my $orig_I = $I; + my $type; + if ( $I =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $I = _nested_constraints($attribute, $1, $2); + } + $metadata_for{$caller}{$attribute}{isa} = [$orig_I, $I]; + my $orig_method = $method; + $method = sub { + if ( $#_ ) { + Mo::_check_type_constaints($attribute, $I, $orig_I, $_[1]); + } + goto &$orig_method; + }; + } + + if ( my $builder = $args{builder} ) { + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$builder + : goto &$original_method + }; + } + + if ( my $code = $args{default} ) { + Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") + unless ref($code) eq 'CODE'; + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$code + : goto &$original_method + }; + } + + if ( my $role = $args{does} ) { + my $original_method = $method; + $method = sub { + if ( $#_ ) { + Carp::confess(qq) + unless blessed($_[1]) && $_[1]->does($role) + } + goto &$original_method + }; + } + + if ( my $coercion = $args{coerce} ) { + $metadata_for{$caller}{$attribute}{coerce} = $coercion; + my $original_method = $method; + $method = sub { + if ( $#_ ) { + return $original_method->($_[0], $coercion->($_[1])) + } + goto &$original_method; + } + } + + $method = $options{$_}->($method, $attribute, @_) + for sort keys %options; + + *{ _glob_for "${caller}::$attribute" } = $method; + + if ( $args{required} ) { + $metadata_for{$caller}{$attribute}{required} = 1; + } + + if ($args{clearer}) { + *{ _glob_for "${caller}::$args{clearer}" } + = sub { delete shift->{$attribute} } + } + + if ($args{predicate}) { + *{ _glob_for "${caller}::$args{predicate}" } + = sub { exists shift->{$attribute} } + } + + if ($args{handles}) { + _has_handles($caller, $attribute, \%args); + } + + if (exists $args{init_arg}) { + $metadata_for{$caller}{$attribute}{init_arg} = $args{init_arg}; + } + } + }, + %exports, + ); + + $export_for{$caller} = [ keys %exports ]; + + for my $keyword ( keys %exports ) { + *{ _glob_for "${caller}::$keyword" } = $exports{$keyword} + } + *{ _glob_for "${caller}::extends" }{CODE}->( "Mo::Object" ) + unless @{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] }; +}; + +sub _check_type_constaints { + my ($attribute, $I, $I_name, $val) = @_; + ( ref($I) eq 'CODE' + ? $I->($val) + : (ref $val eq $I + || ($val && $val eq $I) + || (exists $TYPES{$I} && $TYPES{$I}->($val))) + ) + || Carp::confess( + qq + . qq + . (defined $val ? Mo::Dumper($val) : 'undef') ) +} + +sub _has_handles { + my ($caller, $attribute, $args) = @_; + my $handles = $args->{handles}; + + my $ref = ref $handles; + my $kv; + if ( $ref eq ref [] ) { + $kv = { map { $_,$_ } @{$handles} }; + } + elsif ( $ref eq ref {} ) { + $kv = $handles; + } + elsif ( $ref eq ref qr// ) { + Carp::confess("Cannot delegate methods based on a Regexp without a type constraint (isa)") + unless $args->{isa}; + my $target_class = $args->{isa}; + $kv = { + map { $_, $_ } + grep { $_ =~ $handles } + grep { !exists $Mo::Object::{$_} && $target_class->can($_) } + grep { $_ ne 'has' && $_ ne 'extends' } + keys %{ _stash_for $target_class } + }; + } + else { + Carp::confess("handles for $ref not yet implemented"); } - PTDEBUG && _d("InnoDB version:", $innodb_version); - return $innodb_version; + while ( my ($method, $target) = each %{$kv} ) { + my $name = _glob_for "${caller}::$method"; + Carp::confess("You cannot overwrite a locally defined method ($method) with a delegation") + if defined &$name; + + my ($target, @curried_args) = ref($target) ? @$target : $target; + *$name = sub { + my $self = shift; + my $delegate_to = $self->$attribute(); + my $error = "Cannot delegate $method to $target because the value of $attribute"; + Carp::confess("$error is not defined") unless $delegate_to; + Carp::confess("$error is not an object (got '$delegate_to')") + unless Scalar::Util::blessed($delegate_to) || (!ref($delegate_to) && $delegate_to->can($target)); + return $delegate_to->$target(@curried_args, @_); + } + } } -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"; +sub _nested_constraints { + my ($attribute, $aggregate_type, $type) = @_; + + my $inner_types; + if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $inner_types = _nested_constraints($1, $2); + } + else { + $inner_types = $TYPES{$type}; + } + + if ( $aggregate_type eq 'ArrayRef' ) { + return sub { + my ($val) = @_; + return unless ref($val) eq ref([]); + + if ($inner_types) { + for my $value ( @{$val} ) { + return unless $inner_types->($value) + } + } + else { + for my $value ( @{$val} ) { + return unless $value && ($value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type))); + } + } + return 1; + }; + } + elsif ( $aggregate_type eq 'Maybe' ) { + return sub { + my ($value) = @_; + return 1 if ! defined($value); + if ($inner_types) { + return unless $inner_types->($value) + } + else { + return unless $value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type)); + } + return 1; + } + } + else { + Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); + } } +sub _set_package_isa { + my ($package, @new_isa) = @_; + + *{ _glob_for "${package}::ISA" } = [@new_isa]; +} + +sub _set_inherited_metadata { + my $class = shift; + my $linearized_isa = mro::get_linear_isa($class); + my %new_metadata; + + for my $isa_class (reverse @$linearized_isa) { + %new_metadata = ( + %new_metadata, + %{ $metadata_for{$isa_class} || {} }, + ); + } + $metadata_for{$class} = \%new_metadata; +} + +sub unimport { + my $caller = scalar caller(); + my $stash = _stash_for( $caller ); + + delete $stash->{$_} for @{$export_for{$caller}}; +} + +sub Dumper { + require Data::Dumper; + local $Data::Dumper::Indent = 0; + local $Data::Dumper::Sortkeys = 0; + local $Data::Dumper::Quotekeys = 0; + local $Data::Dumper::Terse = 1; + + Data::Dumper::Dumper(@_) +} + +BEGIN { + if ($] >= 5.010) { + { local $@; require mro; } + } + else { + local $@; + eval { + require MRO::Compat; + } or do { + *mro::get_linear_isa = *mro::get_linear_isa_dfs = sub { + no strict 'refs'; + + my $classname = shift; + + my @lin = ($classname); + my %stored; + foreach my $parent (@{"$classname\::ISA"}) { + my $plin = mro::get_linear_isa_dfs($parent); + foreach (@$plin) { + next if exists $stored{$_}; + push(@lin, $_); + $stored{$_} = 1; + } + } + return \@lin; + }; + } + } +} + +} 1; } # ########################################################################### -# End VersionParser package +# End Mo package # ########################################################################### # ########################################################################### @@ -2179,7 +2485,6 @@ my $oktorun = 1; sub main { @ARGV = @_; # set global ARGV for this package - my $vp = new VersionParser(); $o = new OptionParser(); $o->get_specs(); $o->get_opts(); diff --git a/bin/pt-slave-find b/bin/pt-slave-find index 3a416746..4fe482ee 100755 --- a/bin/pt-slave-find +++ b/bin/pt-slave-find @@ -1033,6 +1033,456 @@ if ( PTDEBUG ) { # End OptionParser package # ########################################################################### +# ########################################################################### +# Mo 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/Mo.pm +# t/lib/Mo.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +BEGIN { +$INC{"Mo.pm"} = __FILE__; +package Mo; +our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. + +{ + no strict 'refs'; + sub _glob_for { + return \*{shift()} + } + + sub _stash_for { + return \%{ shift() . "::" }; + } +} + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util (); + +our %TYPES = ( + Bool => sub { !$_[0] || (defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == 1) }, + Num => sub { defined $_[0] && &Scalar::Util::looks_like_number }, + Int => sub { defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == int $_[0] }, + Str => sub { defined $_[0] }, + Object => sub { defined $_[0] && &Scalar::Util::blessed }, + FileHandle => sub { local $@; require IO::Handle; fileno($_[0]) && $_[0]->opened }, + + map { + my $type = /R/ ? $_ : uc $_; + $_ . "Ref" => sub { ref $_[0] eq $type } + } qw(Array Code Hash Regexp Glob Scalar) +); + +our %metadata_for; +{ + package Mo::Object; + + sub new { + my $class = shift; + my $args = $class->BUILDARGS(@_); + + my @args_to_delete; + while ( my ($attr, $meta) = each %{$metadata_for{$class}} ) { + next unless exists $meta->{init_arg}; + my $init_arg = $meta->{init_arg}; + + if ( defined $init_arg ) { + $args->{$attr} = delete $args->{$init_arg}; + } + else { + push @args_to_delete, $attr; + } + } + + delete $args->{$_} for @args_to_delete; + + for my $attribute ( keys %$args ) { + if ( my $coerce = $metadata_for{$class}{$attribute}{coerce} ) { + $args->{$attribute} = $coerce->($args->{$attribute}); + } + if ( my $I = $metadata_for{$class}{$attribute}{isa} ) { + ( (my $I_name), $I ) = @{$I}; + Mo::_check_type_constaints($attribute, $I, $I_name, $args->{$attribute}); + } + } + + while ( my ($attribute, $meta) = each %{$metadata_for{$class}} ) { + next unless $meta->{required}; + Carp::confess("Attribute ($attribute) is required for $class") + if ! exists $args->{$attribute} + } + + @_ = %$args; + my $self = bless $args, $class; + + my @build_subs; + my $linearized_isa = mro::get_linear_isa($class); + + for my $isa_class ( @$linearized_isa ) { + unshift @build_subs, *{ Mo::_glob_for "${isa_class}::BUILD" }{CODE}; + } + exists &$_ && $_->( $self, @_ ) for grep { defined } @build_subs; + return $self; + } + + sub BUILDARGS { + shift; + my $ref; + if ( @_ == 1 && ref($_[0]) ) { + Carp::confess("Single parameters to new() must be a HASH ref") + unless ref($_[0]) eq ref({}); + $ref = {%{$_[0]}} # We want a new reference, always + } + else { + $ref = { @_ }; + } + return $ref; + } +} + +my %export_for; +sub Mo::import { + warnings->import(qw(FATAL all)); + strict->import(); + + my $caller = scalar caller(); # Caller's package + my $caller_pkg = $caller . "::"; # Caller's package with :: at the end + my (%exports, %options); + + my (undef, @features) = @_; + my %ignore = ( map { $_ => 1 } qw( is isa init_arg builder buildargs clearer predicate build handles default required ) ); + for my $feature (grep { !$ignore{$_} } @features) { + { local $@; require "Mo/$feature.pm"; } + { + no strict 'refs'; + &{"Mo::${feature}::e"}( + $caller_pkg, + \%exports, + \%options, + \@_ + ); + } + } + + return if $exports{M}; + + %exports = ( + extends => sub { + for my $class ( map { "$_" } @_ ) { + $class =~ s{::|'}{/}g; + { local $@; eval { require "$class.pm" } } # or warn $@; + } + _set_package_isa($caller, @_); + _set_inherited_metadata($caller); + }, + has => sub { + my $names = shift; + for my $attribute ( ref $names ? @$names : $names ) { + my %args = @_; + my $method = ($args{is} || '') eq 'ro' + ? sub { + Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller_pkg}${attribute}") + if $#_; + return $_[0]{$attribute}; + } + : sub { + return $#_ + ? $_[0]{$attribute} = $_[1] + : $_[0]{$attribute}; + }; + + $metadata_for{$caller}{$attribute} = (); + + if ( my $I = $args{isa} ) { + my $orig_I = $I; + my $type; + if ( $I =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $I = _nested_constraints($attribute, $1, $2); + } + $metadata_for{$caller}{$attribute}{isa} = [$orig_I, $I]; + my $orig_method = $method; + $method = sub { + if ( $#_ ) { + Mo::_check_type_constaints($attribute, $I, $orig_I, $_[1]); + } + goto &$orig_method; + }; + } + + if ( my $builder = $args{builder} ) { + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$builder + : goto &$original_method + }; + } + + if ( my $code = $args{default} ) { + Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") + unless ref($code) eq 'CODE'; + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$code + : goto &$original_method + }; + } + + if ( my $role = $args{does} ) { + my $original_method = $method; + $method = sub { + if ( $#_ ) { + Carp::confess(qq) + unless blessed($_[1]) && $_[1]->does($role) + } + goto &$original_method + }; + } + + if ( my $coercion = $args{coerce} ) { + $metadata_for{$caller}{$attribute}{coerce} = $coercion; + my $original_method = $method; + $method = sub { + if ( $#_ ) { + return $original_method->($_[0], $coercion->($_[1])) + } + goto &$original_method; + } + } + + $method = $options{$_}->($method, $attribute, @_) + for sort keys %options; + + *{ _glob_for "${caller}::$attribute" } = $method; + + if ( $args{required} ) { + $metadata_for{$caller}{$attribute}{required} = 1; + } + + if ($args{clearer}) { + *{ _glob_for "${caller}::$args{clearer}" } + = sub { delete shift->{$attribute} } + } + + if ($args{predicate}) { + *{ _glob_for "${caller}::$args{predicate}" } + = sub { exists shift->{$attribute} } + } + + if ($args{handles}) { + _has_handles($caller, $attribute, \%args); + } + + if (exists $args{init_arg}) { + $metadata_for{$caller}{$attribute}{init_arg} = $args{init_arg}; + } + } + }, + %exports, + ); + + $export_for{$caller} = [ keys %exports ]; + + for my $keyword ( keys %exports ) { + *{ _glob_for "${caller}::$keyword" } = $exports{$keyword} + } + *{ _glob_for "${caller}::extends" }{CODE}->( "Mo::Object" ) + unless @{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] }; +}; + +sub _check_type_constaints { + my ($attribute, $I, $I_name, $val) = @_; + ( ref($I) eq 'CODE' + ? $I->($val) + : (ref $val eq $I + || ($val && $val eq $I) + || (exists $TYPES{$I} && $TYPES{$I}->($val))) + ) + || Carp::confess( + qq + . qq + . (defined $val ? Mo::Dumper($val) : 'undef') ) +} + +sub _has_handles { + my ($caller, $attribute, $args) = @_; + my $handles = $args->{handles}; + + my $ref = ref $handles; + my $kv; + if ( $ref eq ref [] ) { + $kv = { map { $_,$_ } @{$handles} }; + } + elsif ( $ref eq ref {} ) { + $kv = $handles; + } + elsif ( $ref eq ref qr// ) { + Carp::confess("Cannot delegate methods based on a Regexp without a type constraint (isa)") + unless $args->{isa}; + my $target_class = $args->{isa}; + $kv = { + map { $_, $_ } + grep { $_ =~ $handles } + grep { !exists $Mo::Object::{$_} && $target_class->can($_) } + grep { $_ ne 'has' && $_ ne 'extends' } + keys %{ _stash_for $target_class } + }; + } + else { + Carp::confess("handles for $ref not yet implemented"); + } + + while ( my ($method, $target) = each %{$kv} ) { + my $name = _glob_for "${caller}::$method"; + Carp::confess("You cannot overwrite a locally defined method ($method) with a delegation") + if defined &$name; + + my ($target, @curried_args) = ref($target) ? @$target : $target; + *$name = sub { + my $self = shift; + my $delegate_to = $self->$attribute(); + my $error = "Cannot delegate $method to $target because the value of $attribute"; + Carp::confess("$error is not defined") unless $delegate_to; + Carp::confess("$error is not an object (got '$delegate_to')") + unless Scalar::Util::blessed($delegate_to) || (!ref($delegate_to) && $delegate_to->can($target)); + return $delegate_to->$target(@curried_args, @_); + } + } +} + +sub _nested_constraints { + my ($attribute, $aggregate_type, $type) = @_; + + my $inner_types; + if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $inner_types = _nested_constraints($1, $2); + } + else { + $inner_types = $TYPES{$type}; + } + + if ( $aggregate_type eq 'ArrayRef' ) { + return sub { + my ($val) = @_; + return unless ref($val) eq ref([]); + + if ($inner_types) { + for my $value ( @{$val} ) { + return unless $inner_types->($value) + } + } + else { + for my $value ( @{$val} ) { + return unless $value && ($value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type))); + } + } + return 1; + }; + } + elsif ( $aggregate_type eq 'Maybe' ) { + return sub { + my ($value) = @_; + return 1 if ! defined($value); + if ($inner_types) { + return unless $inner_types->($value) + } + else { + return unless $value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type)); + } + return 1; + } + } + else { + Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); + } +} + +sub _set_package_isa { + my ($package, @new_isa) = @_; + + *{ _glob_for "${package}::ISA" } = [@new_isa]; +} + +sub _set_inherited_metadata { + my $class = shift; + my $linearized_isa = mro::get_linear_isa($class); + my %new_metadata; + + for my $isa_class (reverse @$linearized_isa) { + %new_metadata = ( + %new_metadata, + %{ $metadata_for{$isa_class} || {} }, + ); + } + $metadata_for{$class} = \%new_metadata; +} + +sub unimport { + my $caller = scalar caller(); + my $stash = _stash_for( $caller ); + + delete $stash->{$_} for @{$export_for{$caller}}; +} + +sub Dumper { + require Data::Dumper; + local $Data::Dumper::Indent = 0; + local $Data::Dumper::Sortkeys = 0; + local $Data::Dumper::Quotekeys = 0; + local $Data::Dumper::Terse = 1; + + Data::Dumper::Dumper(@_) +} + +BEGIN { + if ($] >= 5.010) { + { local $@; require mro; } + } + else { + local $@; + eval { + require MRO::Compat; + } or do { + *mro::get_linear_isa = *mro::get_linear_isa_dfs = sub { + no strict 'refs'; + + my $classname = shift; + + my @lin = ($classname); + my %stored; + foreach my $parent (@{"$classname\::ISA"}) { + my $plin = mro::get_linear_isa_dfs($parent); + foreach (@$plin) { + next if exists $stored{$_}; + push(@lin, $_); + $stored{$_} = 1; + } + } + return \@lin; + }; + } + } +} + +} +1; +} +# ########################################################################### +# End Mo package +# ########################################################################### + # ########################################################################### # DSNParser package # This package is a copy without comments from the original. The original @@ -1608,8 +2058,7 @@ sub get_connected_slaves { my $show = "SHOW GRANTS FOR "; my $user = 'CURRENT_USER()'; - my $vp = $self->{VersionParser}; - if ( $vp && !$vp->version_ge($dbh, '4.1.2') ) { + if ( VersionParser->new($dbh) < '4.1.2' ) { $user = $dbh->selectrow_arrayref('SELECT USER()')->[0]; $user =~ s/([^@]+)@(.+)/'$1'\@'$2'/; } @@ -2303,96 +2752,137 @@ sub _d { { package VersionParser; -use strict; -use warnings FATAL => 'all'; +use Mo; +use Scalar::Util qw(blessed); use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; -sub new { - my ( $class ) = @_; - bless {}, $class; -} +use overload ( + '""' => "version", + '<=>' => "cmp", + 'cmp' => "cmp", + fallback => 1, +); -sub parse { - my ( $self, $str ) = @_; - 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; -} +our $VERSION = 0.01; -sub version_cmp { - my ($self, $dbh, $target, $cmp) = @_; - my $version = $self->version($dbh); - my $result; +has major => ( + is => 'ro', + isa => 'Int', + required => 1, +); - 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" - } +has [qw( minor revision )] => ( + is => 'ro', + isa => 'Num', +); - PTDEBUG && _d($self->{$dbh}, $cmp, $target, ':', $result); - return $result; -} +has flavor => ( + is => 'ro', + isa => 'Str', + default => sub { 'Unknown' }, +); -sub version_ge { - my ( $self, $dbh, $target ) = @_; - return $self->version_cmp($dbh, $target, 'ge'); -} +has innodb_version => ( + is => 'ro', + isa => 'Str', + default => sub { 'NO' }, +); -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 series { + my $self = shift; + return $self->_join_version($self->major, $self->minor); } sub version { - my ( $self, $dbh ) = @_; - if ( !$self->{$dbh} ) { - $self->{$dbh} = $self->parse( - $dbh->selectrow_array('SELECT VERSION()')); - } - return $self->{$dbh}; + my $self = shift; + return $self->_join_version($self->major, $self->minor, $self->revision); } -sub innodb_version { +sub is_in { + my ($self, $target) = @_; + + return $self eq $target; +} + +sub _join_version { + my ($self, @parts) = @_; + + return join ".", map { my $c = $_; $c =~ s/^0\./0/; $c } grep defined, @parts; +} +sub _split_version { + my ($self, $str) = @_; + my @version_parts = map { s/^0(?=\d)/0./; $_ } $str =~ m/(\d+)/g; + return @version_parts[0..2]; +} + +sub normalized_version { + my ( $self ) = @_; + my @version_parts = map { $_ || 0 } $self->_split_version( $self->version ); + my $result = sprintf('%d%02d%02d', @version_parts); + PTDEBUG && _d($self->version, 'normalizes to', $result); + return $result; +} + +sub comment { + my ( $self, $cmd ) = @_; + my $v = $self->normalized_version(); + + return "/*!$v $cmd */" +} + +my @methods = qw(major minor revision); +sub cmp { + my ($left, $right) = @_; + my $right_obj = (blessed($right) && $right->isa(ref($left))) + ? $right + : ref($left)->new($right); + + my $retval = 0; + for my $m ( @methods ) { + last unless defined($left->$m) && defined($right_obj->$m); + $retval = $left->$m <=> $right_obj->$m; + last if $retval; + } + return $retval; +} + +sub BUILDARGS { + my $self = shift; + + if ( @_ == 1 ) { + my %args; + if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { + my $dbh = $_[0]; + my $query; + PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); + if ( eval { $query = $dbh->selectall_hashref(q) } ) { + @args{@methods} = $self->_split_version($query->{version}); + $args{flavor} = delete $query->{version_comment} + if $query->{version_comment}; + } + elsif ( eval { ($query) = $dbh->selectrow_array('SELECT VERSION()') } ) { + @args{@methods} = $self->_split_version($query); + } + else { + PTDEBUG && _d("Couldn't get the version from the dbh: $@"); + } + $args{innodb_version} = eval { $self->_innodb_version($dbh) }; + } + elsif ( !ref($_[0]) ) { + @args{@methods} = $self->_split_version($_[0]); + } + + for my $method (@methods) { + delete $args{$method} unless defined $args{$method}; + } + @_ = %args if %args; + } + + return $self->SUPER::BUILDARGS(@_); +} + +sub _innodb_version { my ( $self, $dbh ) = @_; return unless $dbh; my $innodb_version = "NO"; @@ -2430,6 +2920,7 @@ sub _d { print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; } +no Mo; 1; } # ########################################################################### @@ -2850,8 +3341,7 @@ sub main { # Despite the name, recursing to slaves actually begins at the specified # server, so the named server may also be included. - my $vp = new VersionParser(); - my $ms = new MasterSlave(VersionParser => $vp); + my $ms = new MasterSlave(); $ms->recurse_to_slaves( { dbh => $dbh, dsn => $master_dsn, @@ -2888,7 +3378,6 @@ sub main { node => $root, print_node => $print_node, MasterSlave => $ms, - VersionParser => $vp, ); return 0; @@ -2951,8 +3440,8 @@ sub print_node_hostname { sub print_node_summary { my ( %args ) = @_; - my ($ms, $vp, $node, $level) - = @args{qw(MasterSlave VersionParser node level)}; + my ($ms, $node, $level) + = @args{qw(MasterSlave node level)}; die "I need a node" unless $node; $level ||= 0; @@ -3023,7 +3512,7 @@ sub print_node_summary { . ", offset " . ($vars->{auto_increment_offset}->{value} || '') ]; - my $innodb_version = $vp->innodb_version($dbh); + my $innodb_version = VersionParser->new($dbh)->innodb_version(); push @lines, ['InnoDB version', $innodb_version]; my $line_fmt = "$indent%-15s %s"; diff --git a/bin/pt-slave-restart b/bin/pt-slave-restart index 6bbda77d..7b897e51 100755 --- a/bin/pt-slave-restart +++ b/bin/pt-slave-restart @@ -1152,6 +1152,456 @@ if ( PTDEBUG ) { # End OptionParser package # ########################################################################### +# ########################################################################### +# Mo 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/Mo.pm +# t/lib/Mo.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +BEGIN { +$INC{"Mo.pm"} = __FILE__; +package Mo; +our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. + +{ + no strict 'refs'; + sub _glob_for { + return \*{shift()} + } + + sub _stash_for { + return \%{ shift() . "::" }; + } +} + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util (); + +our %TYPES = ( + Bool => sub { !$_[0] || (defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == 1) }, + Num => sub { defined $_[0] && &Scalar::Util::looks_like_number }, + Int => sub { defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == int $_[0] }, + Str => sub { defined $_[0] }, + Object => sub { defined $_[0] && &Scalar::Util::blessed }, + FileHandle => sub { local $@; require IO::Handle; fileno($_[0]) && $_[0]->opened }, + + map { + my $type = /R/ ? $_ : uc $_; + $_ . "Ref" => sub { ref $_[0] eq $type } + } qw(Array Code Hash Regexp Glob Scalar) +); + +our %metadata_for; +{ + package Mo::Object; + + sub new { + my $class = shift; + my $args = $class->BUILDARGS(@_); + + my @args_to_delete; + while ( my ($attr, $meta) = each %{$metadata_for{$class}} ) { + next unless exists $meta->{init_arg}; + my $init_arg = $meta->{init_arg}; + + if ( defined $init_arg ) { + $args->{$attr} = delete $args->{$init_arg}; + } + else { + push @args_to_delete, $attr; + } + } + + delete $args->{$_} for @args_to_delete; + + for my $attribute ( keys %$args ) { + if ( my $coerce = $metadata_for{$class}{$attribute}{coerce} ) { + $args->{$attribute} = $coerce->($args->{$attribute}); + } + if ( my $I = $metadata_for{$class}{$attribute}{isa} ) { + ( (my $I_name), $I ) = @{$I}; + Mo::_check_type_constaints($attribute, $I, $I_name, $args->{$attribute}); + } + } + + while ( my ($attribute, $meta) = each %{$metadata_for{$class}} ) { + next unless $meta->{required}; + Carp::confess("Attribute ($attribute) is required for $class") + if ! exists $args->{$attribute} + } + + @_ = %$args; + my $self = bless $args, $class; + + my @build_subs; + my $linearized_isa = mro::get_linear_isa($class); + + for my $isa_class ( @$linearized_isa ) { + unshift @build_subs, *{ Mo::_glob_for "${isa_class}::BUILD" }{CODE}; + } + exists &$_ && $_->( $self, @_ ) for grep { defined } @build_subs; + return $self; + } + + sub BUILDARGS { + shift; + my $ref; + if ( @_ == 1 && ref($_[0]) ) { + Carp::confess("Single parameters to new() must be a HASH ref") + unless ref($_[0]) eq ref({}); + $ref = {%{$_[0]}} # We want a new reference, always + } + else { + $ref = { @_ }; + } + return $ref; + } +} + +my %export_for; +sub Mo::import { + warnings->import(qw(FATAL all)); + strict->import(); + + my $caller = scalar caller(); # Caller's package + my $caller_pkg = $caller . "::"; # Caller's package with :: at the end + my (%exports, %options); + + my (undef, @features) = @_; + my %ignore = ( map { $_ => 1 } qw( is isa init_arg builder buildargs clearer predicate build handles default required ) ); + for my $feature (grep { !$ignore{$_} } @features) { + { local $@; require "Mo/$feature.pm"; } + { + no strict 'refs'; + &{"Mo::${feature}::e"}( + $caller_pkg, + \%exports, + \%options, + \@_ + ); + } + } + + return if $exports{M}; + + %exports = ( + extends => sub { + for my $class ( map { "$_" } @_ ) { + $class =~ s{::|'}{/}g; + { local $@; eval { require "$class.pm" } } # or warn $@; + } + _set_package_isa($caller, @_); + _set_inherited_metadata($caller); + }, + has => sub { + my $names = shift; + for my $attribute ( ref $names ? @$names : $names ) { + my %args = @_; + my $method = ($args{is} || '') eq 'ro' + ? sub { + Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller_pkg}${attribute}") + if $#_; + return $_[0]{$attribute}; + } + : sub { + return $#_ + ? $_[0]{$attribute} = $_[1] + : $_[0]{$attribute}; + }; + + $metadata_for{$caller}{$attribute} = (); + + if ( my $I = $args{isa} ) { + my $orig_I = $I; + my $type; + if ( $I =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $I = _nested_constraints($attribute, $1, $2); + } + $metadata_for{$caller}{$attribute}{isa} = [$orig_I, $I]; + my $orig_method = $method; + $method = sub { + if ( $#_ ) { + Mo::_check_type_constaints($attribute, $I, $orig_I, $_[1]); + } + goto &$orig_method; + }; + } + + if ( my $builder = $args{builder} ) { + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$builder + : goto &$original_method + }; + } + + if ( my $code = $args{default} ) { + Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") + unless ref($code) eq 'CODE'; + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$code + : goto &$original_method + }; + } + + if ( my $role = $args{does} ) { + my $original_method = $method; + $method = sub { + if ( $#_ ) { + Carp::confess(qq) + unless blessed($_[1]) && $_[1]->does($role) + } + goto &$original_method + }; + } + + if ( my $coercion = $args{coerce} ) { + $metadata_for{$caller}{$attribute}{coerce} = $coercion; + my $original_method = $method; + $method = sub { + if ( $#_ ) { + return $original_method->($_[0], $coercion->($_[1])) + } + goto &$original_method; + } + } + + $method = $options{$_}->($method, $attribute, @_) + for sort keys %options; + + *{ _glob_for "${caller}::$attribute" } = $method; + + if ( $args{required} ) { + $metadata_for{$caller}{$attribute}{required} = 1; + } + + if ($args{clearer}) { + *{ _glob_for "${caller}::$args{clearer}" } + = sub { delete shift->{$attribute} } + } + + if ($args{predicate}) { + *{ _glob_for "${caller}::$args{predicate}" } + = sub { exists shift->{$attribute} } + } + + if ($args{handles}) { + _has_handles($caller, $attribute, \%args); + } + + if (exists $args{init_arg}) { + $metadata_for{$caller}{$attribute}{init_arg} = $args{init_arg}; + } + } + }, + %exports, + ); + + $export_for{$caller} = [ keys %exports ]; + + for my $keyword ( keys %exports ) { + *{ _glob_for "${caller}::$keyword" } = $exports{$keyword} + } + *{ _glob_for "${caller}::extends" }{CODE}->( "Mo::Object" ) + unless @{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] }; +}; + +sub _check_type_constaints { + my ($attribute, $I, $I_name, $val) = @_; + ( ref($I) eq 'CODE' + ? $I->($val) + : (ref $val eq $I + || ($val && $val eq $I) + || (exists $TYPES{$I} && $TYPES{$I}->($val))) + ) + || Carp::confess( + qq + . qq + . (defined $val ? Mo::Dumper($val) : 'undef') ) +} + +sub _has_handles { + my ($caller, $attribute, $args) = @_; + my $handles = $args->{handles}; + + my $ref = ref $handles; + my $kv; + if ( $ref eq ref [] ) { + $kv = { map { $_,$_ } @{$handles} }; + } + elsif ( $ref eq ref {} ) { + $kv = $handles; + } + elsif ( $ref eq ref qr// ) { + Carp::confess("Cannot delegate methods based on a Regexp without a type constraint (isa)") + unless $args->{isa}; + my $target_class = $args->{isa}; + $kv = { + map { $_, $_ } + grep { $_ =~ $handles } + grep { !exists $Mo::Object::{$_} && $target_class->can($_) } + grep { $_ ne 'has' && $_ ne 'extends' } + keys %{ _stash_for $target_class } + }; + } + else { + Carp::confess("handles for $ref not yet implemented"); + } + + while ( my ($method, $target) = each %{$kv} ) { + my $name = _glob_for "${caller}::$method"; + Carp::confess("You cannot overwrite a locally defined method ($method) with a delegation") + if defined &$name; + + my ($target, @curried_args) = ref($target) ? @$target : $target; + *$name = sub { + my $self = shift; + my $delegate_to = $self->$attribute(); + my $error = "Cannot delegate $method to $target because the value of $attribute"; + Carp::confess("$error is not defined") unless $delegate_to; + Carp::confess("$error is not an object (got '$delegate_to')") + unless Scalar::Util::blessed($delegate_to) || (!ref($delegate_to) && $delegate_to->can($target)); + return $delegate_to->$target(@curried_args, @_); + } + } +} + +sub _nested_constraints { + my ($attribute, $aggregate_type, $type) = @_; + + my $inner_types; + if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $inner_types = _nested_constraints($1, $2); + } + else { + $inner_types = $TYPES{$type}; + } + + if ( $aggregate_type eq 'ArrayRef' ) { + return sub { + my ($val) = @_; + return unless ref($val) eq ref([]); + + if ($inner_types) { + for my $value ( @{$val} ) { + return unless $inner_types->($value) + } + } + else { + for my $value ( @{$val} ) { + return unless $value && ($value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type))); + } + } + return 1; + }; + } + elsif ( $aggregate_type eq 'Maybe' ) { + return sub { + my ($value) = @_; + return 1 if ! defined($value); + if ($inner_types) { + return unless $inner_types->($value) + } + else { + return unless $value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type)); + } + return 1; + } + } + else { + Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); + } +} + +sub _set_package_isa { + my ($package, @new_isa) = @_; + + *{ _glob_for "${package}::ISA" } = [@new_isa]; +} + +sub _set_inherited_metadata { + my $class = shift; + my $linearized_isa = mro::get_linear_isa($class); + my %new_metadata; + + for my $isa_class (reverse @$linearized_isa) { + %new_metadata = ( + %new_metadata, + %{ $metadata_for{$isa_class} || {} }, + ); + } + $metadata_for{$class} = \%new_metadata; +} + +sub unimport { + my $caller = scalar caller(); + my $stash = _stash_for( $caller ); + + delete $stash->{$_} for @{$export_for{$caller}}; +} + +sub Dumper { + require Data::Dumper; + local $Data::Dumper::Indent = 0; + local $Data::Dumper::Sortkeys = 0; + local $Data::Dumper::Quotekeys = 0; + local $Data::Dumper::Terse = 1; + + Data::Dumper::Dumper(@_) +} + +BEGIN { + if ($] >= 5.010) { + { local $@; require mro; } + } + else { + local $@; + eval { + require MRO::Compat; + } or do { + *mro::get_linear_isa = *mro::get_linear_isa_dfs = sub { + no strict 'refs'; + + my $classname = shift; + + my @lin = ($classname); + my %stored; + foreach my $parent (@{"$classname\::ISA"}) { + my $plin = mro::get_linear_isa_dfs($parent); + foreach (@$plin) { + next if exists $stored{$_}; + push(@lin, $_); + $stored{$_} = 1; + } + } + return \@lin; + }; + } + } +} + +} +1; +} +# ########################################################################### +# End Mo package +# ########################################################################### + # ########################################################################### # VersionParser package # This package is a copy without comments from the original. The original @@ -1163,96 +1613,137 @@ if ( PTDEBUG ) { { package VersionParser; -use strict; -use warnings FATAL => 'all'; +use Mo; +use Scalar::Util qw(blessed); use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; -sub new { - my ( $class ) = @_; - bless {}, $class; -} +use overload ( + '""' => "version", + '<=>' => "cmp", + 'cmp' => "cmp", + fallback => 1, +); -sub parse { - my ( $self, $str ) = @_; - 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; -} +our $VERSION = 0.01; -sub version_cmp { - my ($self, $dbh, $target, $cmp) = @_; - my $version = $self->version($dbh); - my $result; +has major => ( + is => 'ro', + isa => 'Int', + required => 1, +); - 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" - } +has [qw( minor revision )] => ( + is => 'ro', + isa => 'Num', +); - PTDEBUG && _d($self->{$dbh}, $cmp, $target, ':', $result); - return $result; -} +has flavor => ( + is => 'ro', + isa => 'Str', + default => sub { 'Unknown' }, +); -sub version_ge { - my ( $self, $dbh, $target ) = @_; - return $self->version_cmp($dbh, $target, 'ge'); -} +has innodb_version => ( + is => 'ro', + isa => 'Str', + default => sub { 'NO' }, +); -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 series { + my $self = shift; + return $self->_join_version($self->major, $self->minor); } sub version { - my ( $self, $dbh ) = @_; - if ( !$self->{$dbh} ) { - $self->{$dbh} = $self->parse( - $dbh->selectrow_array('SELECT VERSION()')); - } - return $self->{$dbh}; + my $self = shift; + return $self->_join_version($self->major, $self->minor, $self->revision); } -sub innodb_version { +sub is_in { + my ($self, $target) = @_; + + return $self eq $target; +} + +sub _join_version { + my ($self, @parts) = @_; + + return join ".", map { my $c = $_; $c =~ s/^0\./0/; $c } grep defined, @parts; +} +sub _split_version { + my ($self, $str) = @_; + my @version_parts = map { s/^0(?=\d)/0./; $_ } $str =~ m/(\d+)/g; + return @version_parts[0..2]; +} + +sub normalized_version { + my ( $self ) = @_; + my @version_parts = map { $_ || 0 } $self->_split_version( $self->version ); + my $result = sprintf('%d%02d%02d', @version_parts); + PTDEBUG && _d($self->version, 'normalizes to', $result); + return $result; +} + +sub comment { + my ( $self, $cmd ) = @_; + my $v = $self->normalized_version(); + + return "/*!$v $cmd */" +} + +my @methods = qw(major minor revision); +sub cmp { + my ($left, $right) = @_; + my $right_obj = (blessed($right) && $right->isa(ref($left))) + ? $right + : ref($left)->new($right); + + my $retval = 0; + for my $m ( @methods ) { + last unless defined($left->$m) && defined($right_obj->$m); + $retval = $left->$m <=> $right_obj->$m; + last if $retval; + } + return $retval; +} + +sub BUILDARGS { + my $self = shift; + + if ( @_ == 1 ) { + my %args; + if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { + my $dbh = $_[0]; + my $query; + PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); + if ( eval { $query = $dbh->selectall_hashref(q) } ) { + @args{@methods} = $self->_split_version($query->{version}); + $args{flavor} = delete $query->{version_comment} + if $query->{version_comment}; + } + elsif ( eval { ($query) = $dbh->selectrow_array('SELECT VERSION()') } ) { + @args{@methods} = $self->_split_version($query); + } + else { + PTDEBUG && _d("Couldn't get the version from the dbh: $@"); + } + $args{innodb_version} = eval { $self->_innodb_version($dbh) }; + } + elsif ( !ref($_[0]) ) { + @args{@methods} = $self->_split_version($_[0]); + } + + for my $method (@methods) { + delete $args{$method} unless defined $args{$method}; + } + @_ = %args if %args; + } + + return $self->SUPER::BUILDARGS(@_); +} + +sub _innodb_version { my ( $self, $dbh ) = @_; return unless $dbh; my $innodb_version = "NO"; @@ -1290,6 +1781,7 @@ sub _d { print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; } +no Mo; 1; } # ########################################################################### @@ -1871,8 +2363,7 @@ sub get_connected_slaves { my $show = "SHOW GRANTS FOR "; my $user = 'CURRENT_USER()'; - my $vp = $self->{VersionParser}; - if ( $vp && !$vp->version_ge($dbh, '4.1.2') ) { + if ( VersionParser->new($dbh) < '4.1.2' ) { $user = $dbh->selectrow_arrayref('SELECT USER()')->[0]; $user =~ s/([^@]+)@(.+)/'$1'\@'$2'/; } @@ -2578,7 +3069,6 @@ $OUTPUT_AUTOFLUSH = 1; my $o; my $dp; my $q = new Quoter(); -my $vp = new VersionParser(); my %children; sub main { @@ -2674,7 +3164,7 @@ sub main { # Despite the name, recursing to slaves actually begins at the specified # server, so the named server may also be watched, if it's a slave. - my $ms = new MasterSlave(VersionParser => $vp); + my $ms = new MasterSlave(); $ms->recurse_to_slaves( { dbh => $dbh, dsn => $dsn, @@ -2750,7 +3240,7 @@ sub watch_server { PTDEBUG && _d('Watching server', $dp->as_string($dsn), 'forked:', $was_forked); - my $start_sql = $vp->version_ge($dbh, '4.0.5') + my $start_sql = VersionParser->new($dbh) >= '4.0.5' ? 'START SLAVE' : 'SLAVE START'; if ( $o->get('until-master') ) { my ( $file, $pos ) = split(',', $o->get('until-master')); diff --git a/bin/pt-table-checksum b/bin/pt-table-checksum index 0a87c20d..eceaf5d1 100755 --- a/bin/pt-table-checksum +++ b/bin/pt-table-checksum @@ -1394,6 +1394,456 @@ if ( PTDEBUG ) { # End OptionParser package # ########################################################################### +# ########################################################################### +# Mo 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/Mo.pm +# t/lib/Mo.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +BEGIN { +$INC{"Mo.pm"} = __FILE__; +package Mo; +our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. + +{ + no strict 'refs'; + sub _glob_for { + return \*{shift()} + } + + sub _stash_for { + return \%{ shift() . "::" }; + } +} + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util (); + +our %TYPES = ( + Bool => sub { !$_[0] || (defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == 1) }, + Num => sub { defined $_[0] && &Scalar::Util::looks_like_number }, + Int => sub { defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == int $_[0] }, + Str => sub { defined $_[0] }, + Object => sub { defined $_[0] && &Scalar::Util::blessed }, + FileHandle => sub { local $@; require IO::Handle; fileno($_[0]) && $_[0]->opened }, + + map { + my $type = /R/ ? $_ : uc $_; + $_ . "Ref" => sub { ref $_[0] eq $type } + } qw(Array Code Hash Regexp Glob Scalar) +); + +our %metadata_for; +{ + package Mo::Object; + + sub new { + my $class = shift; + my $args = $class->BUILDARGS(@_); + + my @args_to_delete; + while ( my ($attr, $meta) = each %{$metadata_for{$class}} ) { + next unless exists $meta->{init_arg}; + my $init_arg = $meta->{init_arg}; + + if ( defined $init_arg ) { + $args->{$attr} = delete $args->{$init_arg}; + } + else { + push @args_to_delete, $attr; + } + } + + delete $args->{$_} for @args_to_delete; + + for my $attribute ( keys %$args ) { + if ( my $coerce = $metadata_for{$class}{$attribute}{coerce} ) { + $args->{$attribute} = $coerce->($args->{$attribute}); + } + if ( my $I = $metadata_for{$class}{$attribute}{isa} ) { + ( (my $I_name), $I ) = @{$I}; + Mo::_check_type_constaints($attribute, $I, $I_name, $args->{$attribute}); + } + } + + while ( my ($attribute, $meta) = each %{$metadata_for{$class}} ) { + next unless $meta->{required}; + Carp::confess("Attribute ($attribute) is required for $class") + if ! exists $args->{$attribute} + } + + @_ = %$args; + my $self = bless $args, $class; + + my @build_subs; + my $linearized_isa = mro::get_linear_isa($class); + + for my $isa_class ( @$linearized_isa ) { + unshift @build_subs, *{ Mo::_glob_for "${isa_class}::BUILD" }{CODE}; + } + exists &$_ && $_->( $self, @_ ) for grep { defined } @build_subs; + return $self; + } + + sub BUILDARGS { + shift; + my $ref; + if ( @_ == 1 && ref($_[0]) ) { + Carp::confess("Single parameters to new() must be a HASH ref") + unless ref($_[0]) eq ref({}); + $ref = {%{$_[0]}} # We want a new reference, always + } + else { + $ref = { @_ }; + } + return $ref; + } +} + +my %export_for; +sub Mo::import { + warnings->import(qw(FATAL all)); + strict->import(); + + my $caller = scalar caller(); # Caller's package + my $caller_pkg = $caller . "::"; # Caller's package with :: at the end + my (%exports, %options); + + my (undef, @features) = @_; + my %ignore = ( map { $_ => 1 } qw( is isa init_arg builder buildargs clearer predicate build handles default required ) ); + for my $feature (grep { !$ignore{$_} } @features) { + { local $@; require "Mo/$feature.pm"; } + { + no strict 'refs'; + &{"Mo::${feature}::e"}( + $caller_pkg, + \%exports, + \%options, + \@_ + ); + } + } + + return if $exports{M}; + + %exports = ( + extends => sub { + for my $class ( map { "$_" } @_ ) { + $class =~ s{::|'}{/}g; + { local $@; eval { require "$class.pm" } } # or warn $@; + } + _set_package_isa($caller, @_); + _set_inherited_metadata($caller); + }, + has => sub { + my $names = shift; + for my $attribute ( ref $names ? @$names : $names ) { + my %args = @_; + my $method = ($args{is} || '') eq 'ro' + ? sub { + Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller_pkg}${attribute}") + if $#_; + return $_[0]{$attribute}; + } + : sub { + return $#_ + ? $_[0]{$attribute} = $_[1] + : $_[0]{$attribute}; + }; + + $metadata_for{$caller}{$attribute} = (); + + if ( my $I = $args{isa} ) { + my $orig_I = $I; + my $type; + if ( $I =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $I = _nested_constraints($attribute, $1, $2); + } + $metadata_for{$caller}{$attribute}{isa} = [$orig_I, $I]; + my $orig_method = $method; + $method = sub { + if ( $#_ ) { + Mo::_check_type_constaints($attribute, $I, $orig_I, $_[1]); + } + goto &$orig_method; + }; + } + + if ( my $builder = $args{builder} ) { + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$builder + : goto &$original_method + }; + } + + if ( my $code = $args{default} ) { + Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") + unless ref($code) eq 'CODE'; + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$code + : goto &$original_method + }; + } + + if ( my $role = $args{does} ) { + my $original_method = $method; + $method = sub { + if ( $#_ ) { + Carp::confess(qq) + unless blessed($_[1]) && $_[1]->does($role) + } + goto &$original_method + }; + } + + if ( my $coercion = $args{coerce} ) { + $metadata_for{$caller}{$attribute}{coerce} = $coercion; + my $original_method = $method; + $method = sub { + if ( $#_ ) { + return $original_method->($_[0], $coercion->($_[1])) + } + goto &$original_method; + } + } + + $method = $options{$_}->($method, $attribute, @_) + for sort keys %options; + + *{ _glob_for "${caller}::$attribute" } = $method; + + if ( $args{required} ) { + $metadata_for{$caller}{$attribute}{required} = 1; + } + + if ($args{clearer}) { + *{ _glob_for "${caller}::$args{clearer}" } + = sub { delete shift->{$attribute} } + } + + if ($args{predicate}) { + *{ _glob_for "${caller}::$args{predicate}" } + = sub { exists shift->{$attribute} } + } + + if ($args{handles}) { + _has_handles($caller, $attribute, \%args); + } + + if (exists $args{init_arg}) { + $metadata_for{$caller}{$attribute}{init_arg} = $args{init_arg}; + } + } + }, + %exports, + ); + + $export_for{$caller} = [ keys %exports ]; + + for my $keyword ( keys %exports ) { + *{ _glob_for "${caller}::$keyword" } = $exports{$keyword} + } + *{ _glob_for "${caller}::extends" }{CODE}->( "Mo::Object" ) + unless @{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] }; +}; + +sub _check_type_constaints { + my ($attribute, $I, $I_name, $val) = @_; + ( ref($I) eq 'CODE' + ? $I->($val) + : (ref $val eq $I + || ($val && $val eq $I) + || (exists $TYPES{$I} && $TYPES{$I}->($val))) + ) + || Carp::confess( + qq + . qq + . (defined $val ? Mo::Dumper($val) : 'undef') ) +} + +sub _has_handles { + my ($caller, $attribute, $args) = @_; + my $handles = $args->{handles}; + + my $ref = ref $handles; + my $kv; + if ( $ref eq ref [] ) { + $kv = { map { $_,$_ } @{$handles} }; + } + elsif ( $ref eq ref {} ) { + $kv = $handles; + } + elsif ( $ref eq ref qr// ) { + Carp::confess("Cannot delegate methods based on a Regexp without a type constraint (isa)") + unless $args->{isa}; + my $target_class = $args->{isa}; + $kv = { + map { $_, $_ } + grep { $_ =~ $handles } + grep { !exists $Mo::Object::{$_} && $target_class->can($_) } + grep { $_ ne 'has' && $_ ne 'extends' } + keys %{ _stash_for $target_class } + }; + } + else { + Carp::confess("handles for $ref not yet implemented"); + } + + while ( my ($method, $target) = each %{$kv} ) { + my $name = _glob_for "${caller}::$method"; + Carp::confess("You cannot overwrite a locally defined method ($method) with a delegation") + if defined &$name; + + my ($target, @curried_args) = ref($target) ? @$target : $target; + *$name = sub { + my $self = shift; + my $delegate_to = $self->$attribute(); + my $error = "Cannot delegate $method to $target because the value of $attribute"; + Carp::confess("$error is not defined") unless $delegate_to; + Carp::confess("$error is not an object (got '$delegate_to')") + unless Scalar::Util::blessed($delegate_to) || (!ref($delegate_to) && $delegate_to->can($target)); + return $delegate_to->$target(@curried_args, @_); + } + } +} + +sub _nested_constraints { + my ($attribute, $aggregate_type, $type) = @_; + + my $inner_types; + if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $inner_types = _nested_constraints($1, $2); + } + else { + $inner_types = $TYPES{$type}; + } + + if ( $aggregate_type eq 'ArrayRef' ) { + return sub { + my ($val) = @_; + return unless ref($val) eq ref([]); + + if ($inner_types) { + for my $value ( @{$val} ) { + return unless $inner_types->($value) + } + } + else { + for my $value ( @{$val} ) { + return unless $value && ($value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type))); + } + } + return 1; + }; + } + elsif ( $aggregate_type eq 'Maybe' ) { + return sub { + my ($value) = @_; + return 1 if ! defined($value); + if ($inner_types) { + return unless $inner_types->($value) + } + else { + return unless $value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type)); + } + return 1; + } + } + else { + Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); + } +} + +sub _set_package_isa { + my ($package, @new_isa) = @_; + + *{ _glob_for "${package}::ISA" } = [@new_isa]; +} + +sub _set_inherited_metadata { + my $class = shift; + my $linearized_isa = mro::get_linear_isa($class); + my %new_metadata; + + for my $isa_class (reverse @$linearized_isa) { + %new_metadata = ( + %new_metadata, + %{ $metadata_for{$isa_class} || {} }, + ); + } + $metadata_for{$class} = \%new_metadata; +} + +sub unimport { + my $caller = scalar caller(); + my $stash = _stash_for( $caller ); + + delete $stash->{$_} for @{$export_for{$caller}}; +} + +sub Dumper { + require Data::Dumper; + local $Data::Dumper::Indent = 0; + local $Data::Dumper::Sortkeys = 0; + local $Data::Dumper::Quotekeys = 0; + local $Data::Dumper::Terse = 1; + + Data::Dumper::Dumper(@_) +} + +BEGIN { + if ($] >= 5.010) { + { local $@; require mro; } + } + else { + local $@; + eval { + require MRO::Compat; + } or do { + *mro::get_linear_isa = *mro::get_linear_isa_dfs = sub { + no strict 'refs'; + + my $classname = shift; + + my @lin = ($classname); + my %stored; + foreach my $parent (@{"$classname\::ISA"}) { + my $plin = mro::get_linear_isa_dfs($parent); + foreach (@$plin) { + next if exists $stored{$_}; + push(@lin, $_); + $stored{$_} = 1; + } + } + return \@lin; + }; + } + } +} + +} +1; +} +# ########################################################################### +# End Mo package +# ########################################################################### + # ########################################################################### # Cxn package # This package is a copy without comments from the original. The original @@ -1668,96 +2118,137 @@ sub deserialize_list { { package VersionParser; -use strict; -use warnings FATAL => 'all'; +use Mo; +use Scalar::Util qw(blessed); use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; -sub new { - my ( $class ) = @_; - bless {}, $class; -} +use overload ( + '""' => "version", + '<=>' => "cmp", + 'cmp' => "cmp", + fallback => 1, +); -sub parse { - my ( $self, $str ) = @_; - 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; -} +our $VERSION = 0.01; -sub version_cmp { - my ($self, $dbh, $target, $cmp) = @_; - my $version = $self->version($dbh); - my $result; +has major => ( + is => 'ro', + isa => 'Int', + required => 1, +); - 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" - } +has [qw( minor revision )] => ( + is => 'ro', + isa => 'Num', +); - PTDEBUG && _d($self->{$dbh}, $cmp, $target, ':', $result); - return $result; -} +has flavor => ( + is => 'ro', + isa => 'Str', + default => sub { 'Unknown' }, +); -sub version_ge { - my ( $self, $dbh, $target ) = @_; - return $self->version_cmp($dbh, $target, 'ge'); -} +has innodb_version => ( + is => 'ro', + isa => 'Str', + default => sub { 'NO' }, +); -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 series { + my $self = shift; + return $self->_join_version($self->major, $self->minor); } sub version { - my ( $self, $dbh ) = @_; - if ( !$self->{$dbh} ) { - $self->{$dbh} = $self->parse( - $dbh->selectrow_array('SELECT VERSION()')); - } - return $self->{$dbh}; + my $self = shift; + return $self->_join_version($self->major, $self->minor, $self->revision); } -sub innodb_version { +sub is_in { + my ($self, $target) = @_; + + return $self eq $target; +} + +sub _join_version { + my ($self, @parts) = @_; + + return join ".", map { my $c = $_; $c =~ s/^0\./0/; $c } grep defined, @parts; +} +sub _split_version { + my ($self, $str) = @_; + my @version_parts = map { s/^0(?=\d)/0./; $_ } $str =~ m/(\d+)/g; + return @version_parts[0..2]; +} + +sub normalized_version { + my ( $self ) = @_; + my @version_parts = map { $_ || 0 } $self->_split_version( $self->version ); + my $result = sprintf('%d%02d%02d', @version_parts); + PTDEBUG && _d($self->version, 'normalizes to', $result); + return $result; +} + +sub comment { + my ( $self, $cmd ) = @_; + my $v = $self->normalized_version(); + + return "/*!$v $cmd */" +} + +my @methods = qw(major minor revision); +sub cmp { + my ($left, $right) = @_; + my $right_obj = (blessed($right) && $right->isa(ref($left))) + ? $right + : ref($left)->new($right); + + my $retval = 0; + for my $m ( @methods ) { + last unless defined($left->$m) && defined($right_obj->$m); + $retval = $left->$m <=> $right_obj->$m; + last if $retval; + } + return $retval; +} + +sub BUILDARGS { + my $self = shift; + + if ( @_ == 1 ) { + my %args; + if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { + my $dbh = $_[0]; + my $query; + PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); + if ( eval { $query = $dbh->selectall_hashref(q) } ) { + @args{@methods} = $self->_split_version($query->{version}); + $args{flavor} = delete $query->{version_comment} + if $query->{version_comment}; + } + elsif ( eval { ($query) = $dbh->selectrow_array('SELECT VERSION()') } ) { + @args{@methods} = $self->_split_version($query); + } + else { + PTDEBUG && _d("Couldn't get the version from the dbh: $@"); + } + $args{innodb_version} = eval { $self->_innodb_version($dbh) }; + } + elsif ( !ref($_[0]) ) { + @args{@methods} = $self->_split_version($_[0]); + } + + for my $method (@methods) { + delete $args{$method} unless defined $args{$method}; + } + @_ = %args if %args; + } + + return $self->SUPER::BUILDARGS(@_); +} + +sub _innodb_version { my ( $self, $dbh ) = @_; return unless $dbh; my $innodb_version = "NO"; @@ -1795,6 +2286,7 @@ sub _d { print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; } +no Mo; 1; } # ########################################################################### @@ -2105,7 +2597,7 @@ sub get_keys { my ( $type, $cols ) = $key =~ m/(?:USING (\w+))? \((.+)\)/; my ( $special ) = $key =~ m/(FULLTEXT|SPATIAL)/; $type = $type || $special || 'BTREE'; - if ( $opts->{mysql_version} && $opts->{mysql_version} lt '004001000' + if ( $opts->{mysql_version} && $opts->{mysql_version} lt '4.1' && $engine =~ m/HEAP|MEMORY/i ) { $type = 'HASH'; # MySQL pre-4.1 supports only HASH indexes on HEAP @@ -2724,8 +3216,7 @@ sub get_connected_slaves { my $show = "SHOW GRANTS FOR "; my $user = 'CURRENT_USER()'; - my $vp = $self->{VersionParser}; - if ( $vp && !$vp->version_ge($dbh, '4.1.2') ) { + if ( VersionParser->new($dbh) < '4.1.2' ) { $user = $dbh->selectrow_arrayref('SELECT USER()')->[0]; $user =~ s/([^@]+)@(.+)/'$1'\@'$2'/; } @@ -6374,7 +6865,6 @@ sub main { # ######################################################################## # Connect to the master. # ######################################################################## - my $vp = new VersionParser(); my $set_on_connect = sub { my ($dbh) = @_; @@ -6386,7 +6876,7 @@ sub main { # instead, it should check if it's already set to STATEMENT. # This is becase starting with MySQL 5.1.29, changing the format # requires a SUPER user. - if ( $vp->version_ge($dbh, '5.1.5') ) { + if ( VersionParser->new($dbh) >= '5.1.5' ) { $sql = 'SELECT @@binlog_format'; PTDEBUG && _d($dbh, $sql); my ($original_binlog_format) = $dbh->selectrow_array($sql); @@ -6508,7 +6998,7 @@ sub main { my $q = new Quoter(); my $tp = new TableParser(Quoter => $q); my $rc = new RowChecksum(Quoter=> $q, OptionParser => $o); - my $ms = new MasterSlave(VersionParser => $vp); + my $ms = new MasterSlave(); my $slaves; # all slaves (that we can find) my $slave_lag_cxns; # slaves whose lag we'll check diff --git a/bin/pt-table-sync b/bin/pt-table-sync index 4536f528..81c29fa2 100755 --- a/bin/pt-table-sync +++ b/bin/pt-table-sync @@ -1033,6 +1033,456 @@ if ( PTDEBUG ) { # End OptionParser package # ########################################################################### +# ########################################################################### +# Mo 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/Mo.pm +# t/lib/Mo.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +BEGIN { +$INC{"Mo.pm"} = __FILE__; +package Mo; +our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. + +{ + no strict 'refs'; + sub _glob_for { + return \*{shift()} + } + + sub _stash_for { + return \%{ shift() . "::" }; + } +} + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util (); + +our %TYPES = ( + Bool => sub { !$_[0] || (defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == 1) }, + Num => sub { defined $_[0] && &Scalar::Util::looks_like_number }, + Int => sub { defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == int $_[0] }, + Str => sub { defined $_[0] }, + Object => sub { defined $_[0] && &Scalar::Util::blessed }, + FileHandle => sub { local $@; require IO::Handle; fileno($_[0]) && $_[0]->opened }, + + map { + my $type = /R/ ? $_ : uc $_; + $_ . "Ref" => sub { ref $_[0] eq $type } + } qw(Array Code Hash Regexp Glob Scalar) +); + +our %metadata_for; +{ + package Mo::Object; + + sub new { + my $class = shift; + my $args = $class->BUILDARGS(@_); + + my @args_to_delete; + while ( my ($attr, $meta) = each %{$metadata_for{$class}} ) { + next unless exists $meta->{init_arg}; + my $init_arg = $meta->{init_arg}; + + if ( defined $init_arg ) { + $args->{$attr} = delete $args->{$init_arg}; + } + else { + push @args_to_delete, $attr; + } + } + + delete $args->{$_} for @args_to_delete; + + for my $attribute ( keys %$args ) { + if ( my $coerce = $metadata_for{$class}{$attribute}{coerce} ) { + $args->{$attribute} = $coerce->($args->{$attribute}); + } + if ( my $I = $metadata_for{$class}{$attribute}{isa} ) { + ( (my $I_name), $I ) = @{$I}; + Mo::_check_type_constaints($attribute, $I, $I_name, $args->{$attribute}); + } + } + + while ( my ($attribute, $meta) = each %{$metadata_for{$class}} ) { + next unless $meta->{required}; + Carp::confess("Attribute ($attribute) is required for $class") + if ! exists $args->{$attribute} + } + + @_ = %$args; + my $self = bless $args, $class; + + my @build_subs; + my $linearized_isa = mro::get_linear_isa($class); + + for my $isa_class ( @$linearized_isa ) { + unshift @build_subs, *{ Mo::_glob_for "${isa_class}::BUILD" }{CODE}; + } + exists &$_ && $_->( $self, @_ ) for grep { defined } @build_subs; + return $self; + } + + sub BUILDARGS { + shift; + my $ref; + if ( @_ == 1 && ref($_[0]) ) { + Carp::confess("Single parameters to new() must be a HASH ref") + unless ref($_[0]) eq ref({}); + $ref = {%{$_[0]}} # We want a new reference, always + } + else { + $ref = { @_ }; + } + return $ref; + } +} + +my %export_for; +sub Mo::import { + warnings->import(qw(FATAL all)); + strict->import(); + + my $caller = scalar caller(); # Caller's package + my $caller_pkg = $caller . "::"; # Caller's package with :: at the end + my (%exports, %options); + + my (undef, @features) = @_; + my %ignore = ( map { $_ => 1 } qw( is isa init_arg builder buildargs clearer predicate build handles default required ) ); + for my $feature (grep { !$ignore{$_} } @features) { + { local $@; require "Mo/$feature.pm"; } + { + no strict 'refs'; + &{"Mo::${feature}::e"}( + $caller_pkg, + \%exports, + \%options, + \@_ + ); + } + } + + return if $exports{M}; + + %exports = ( + extends => sub { + for my $class ( map { "$_" } @_ ) { + $class =~ s{::|'}{/}g; + { local $@; eval { require "$class.pm" } } # or warn $@; + } + _set_package_isa($caller, @_); + _set_inherited_metadata($caller); + }, + has => sub { + my $names = shift; + for my $attribute ( ref $names ? @$names : $names ) { + my %args = @_; + my $method = ($args{is} || '') eq 'ro' + ? sub { + Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller_pkg}${attribute}") + if $#_; + return $_[0]{$attribute}; + } + : sub { + return $#_ + ? $_[0]{$attribute} = $_[1] + : $_[0]{$attribute}; + }; + + $metadata_for{$caller}{$attribute} = (); + + if ( my $I = $args{isa} ) { + my $orig_I = $I; + my $type; + if ( $I =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $I = _nested_constraints($attribute, $1, $2); + } + $metadata_for{$caller}{$attribute}{isa} = [$orig_I, $I]; + my $orig_method = $method; + $method = sub { + if ( $#_ ) { + Mo::_check_type_constaints($attribute, $I, $orig_I, $_[1]); + } + goto &$orig_method; + }; + } + + if ( my $builder = $args{builder} ) { + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$builder + : goto &$original_method + }; + } + + if ( my $code = $args{default} ) { + Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") + unless ref($code) eq 'CODE'; + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$code + : goto &$original_method + }; + } + + if ( my $role = $args{does} ) { + my $original_method = $method; + $method = sub { + if ( $#_ ) { + Carp::confess(qq) + unless blessed($_[1]) && $_[1]->does($role) + } + goto &$original_method + }; + } + + if ( my $coercion = $args{coerce} ) { + $metadata_for{$caller}{$attribute}{coerce} = $coercion; + my $original_method = $method; + $method = sub { + if ( $#_ ) { + return $original_method->($_[0], $coercion->($_[1])) + } + goto &$original_method; + } + } + + $method = $options{$_}->($method, $attribute, @_) + for sort keys %options; + + *{ _glob_for "${caller}::$attribute" } = $method; + + if ( $args{required} ) { + $metadata_for{$caller}{$attribute}{required} = 1; + } + + if ($args{clearer}) { + *{ _glob_for "${caller}::$args{clearer}" } + = sub { delete shift->{$attribute} } + } + + if ($args{predicate}) { + *{ _glob_for "${caller}::$args{predicate}" } + = sub { exists shift->{$attribute} } + } + + if ($args{handles}) { + _has_handles($caller, $attribute, \%args); + } + + if (exists $args{init_arg}) { + $metadata_for{$caller}{$attribute}{init_arg} = $args{init_arg}; + } + } + }, + %exports, + ); + + $export_for{$caller} = [ keys %exports ]; + + for my $keyword ( keys %exports ) { + *{ _glob_for "${caller}::$keyword" } = $exports{$keyword} + } + *{ _glob_for "${caller}::extends" }{CODE}->( "Mo::Object" ) + unless @{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] }; +}; + +sub _check_type_constaints { + my ($attribute, $I, $I_name, $val) = @_; + ( ref($I) eq 'CODE' + ? $I->($val) + : (ref $val eq $I + || ($val && $val eq $I) + || (exists $TYPES{$I} && $TYPES{$I}->($val))) + ) + || Carp::confess( + qq + . qq + . (defined $val ? Mo::Dumper($val) : 'undef') ) +} + +sub _has_handles { + my ($caller, $attribute, $args) = @_; + my $handles = $args->{handles}; + + my $ref = ref $handles; + my $kv; + if ( $ref eq ref [] ) { + $kv = { map { $_,$_ } @{$handles} }; + } + elsif ( $ref eq ref {} ) { + $kv = $handles; + } + elsif ( $ref eq ref qr// ) { + Carp::confess("Cannot delegate methods based on a Regexp without a type constraint (isa)") + unless $args->{isa}; + my $target_class = $args->{isa}; + $kv = { + map { $_, $_ } + grep { $_ =~ $handles } + grep { !exists $Mo::Object::{$_} && $target_class->can($_) } + grep { $_ ne 'has' && $_ ne 'extends' } + keys %{ _stash_for $target_class } + }; + } + else { + Carp::confess("handles for $ref not yet implemented"); + } + + while ( my ($method, $target) = each %{$kv} ) { + my $name = _glob_for "${caller}::$method"; + Carp::confess("You cannot overwrite a locally defined method ($method) with a delegation") + if defined &$name; + + my ($target, @curried_args) = ref($target) ? @$target : $target; + *$name = sub { + my $self = shift; + my $delegate_to = $self->$attribute(); + my $error = "Cannot delegate $method to $target because the value of $attribute"; + Carp::confess("$error is not defined") unless $delegate_to; + Carp::confess("$error is not an object (got '$delegate_to')") + unless Scalar::Util::blessed($delegate_to) || (!ref($delegate_to) && $delegate_to->can($target)); + return $delegate_to->$target(@curried_args, @_); + } + } +} + +sub _nested_constraints { + my ($attribute, $aggregate_type, $type) = @_; + + my $inner_types; + if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $inner_types = _nested_constraints($1, $2); + } + else { + $inner_types = $TYPES{$type}; + } + + if ( $aggregate_type eq 'ArrayRef' ) { + return sub { + my ($val) = @_; + return unless ref($val) eq ref([]); + + if ($inner_types) { + for my $value ( @{$val} ) { + return unless $inner_types->($value) + } + } + else { + for my $value ( @{$val} ) { + return unless $value && ($value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type))); + } + } + return 1; + }; + } + elsif ( $aggregate_type eq 'Maybe' ) { + return sub { + my ($value) = @_; + return 1 if ! defined($value); + if ($inner_types) { + return unless $inner_types->($value) + } + else { + return unless $value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type)); + } + return 1; + } + } + else { + Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); + } +} + +sub _set_package_isa { + my ($package, @new_isa) = @_; + + *{ _glob_for "${package}::ISA" } = [@new_isa]; +} + +sub _set_inherited_metadata { + my $class = shift; + my $linearized_isa = mro::get_linear_isa($class); + my %new_metadata; + + for my $isa_class (reverse @$linearized_isa) { + %new_metadata = ( + %new_metadata, + %{ $metadata_for{$isa_class} || {} }, + ); + } + $metadata_for{$class} = \%new_metadata; +} + +sub unimport { + my $caller = scalar caller(); + my $stash = _stash_for( $caller ); + + delete $stash->{$_} for @{$export_for{$caller}}; +} + +sub Dumper { + require Data::Dumper; + local $Data::Dumper::Indent = 0; + local $Data::Dumper::Sortkeys = 0; + local $Data::Dumper::Quotekeys = 0; + local $Data::Dumper::Terse = 1; + + Data::Dumper::Dumper(@_) +} + +BEGIN { + if ($] >= 5.010) { + { local $@; require mro; } + } + else { + local $@; + eval { + require MRO::Compat; + } or do { + *mro::get_linear_isa = *mro::get_linear_isa_dfs = sub { + no strict 'refs'; + + my $classname = shift; + + my @lin = ($classname); + my %stored; + foreach my $parent (@{"$classname\::ISA"}) { + my $plin = mro::get_linear_isa_dfs($parent); + foreach (@$plin) { + next if exists $stored{$_}; + push(@lin, $_); + $stored{$_} = 1; + } + } + return \@lin; + }; + } + } +} + +} +1; +} +# ########################################################################### +# End Mo package +# ########################################################################### + # ########################################################################### # Quoter package # This package is a copy without comments from the original. The original @@ -1524,96 +1974,137 @@ sub _d { { package VersionParser; -use strict; -use warnings FATAL => 'all'; +use Mo; +use Scalar::Util qw(blessed); use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; -sub new { - my ( $class ) = @_; - bless {}, $class; -} +use overload ( + '""' => "version", + '<=>' => "cmp", + 'cmp' => "cmp", + fallback => 1, +); -sub parse { - my ( $self, $str ) = @_; - 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; -} +our $VERSION = 0.01; -sub version_cmp { - my ($self, $dbh, $target, $cmp) = @_; - my $version = $self->version($dbh); - my $result; +has major => ( + is => 'ro', + isa => 'Int', + required => 1, +); - 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" - } +has [qw( minor revision )] => ( + is => 'ro', + isa => 'Num', +); - PTDEBUG && _d($self->{$dbh}, $cmp, $target, ':', $result); - return $result; -} +has flavor => ( + is => 'ro', + isa => 'Str', + default => sub { 'Unknown' }, +); -sub version_ge { - my ( $self, $dbh, $target ) = @_; - return $self->version_cmp($dbh, $target, 'ge'); -} +has innodb_version => ( + is => 'ro', + isa => 'Str', + default => sub { 'NO' }, +); -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 series { + my $self = shift; + return $self->_join_version($self->major, $self->minor); } sub version { - my ( $self, $dbh ) = @_; - if ( !$self->{$dbh} ) { - $self->{$dbh} = $self->parse( - $dbh->selectrow_array('SELECT VERSION()')); - } - return $self->{$dbh}; + my $self = shift; + return $self->_join_version($self->major, $self->minor, $self->revision); } -sub innodb_version { +sub is_in { + my ($self, $target) = @_; + + return $self eq $target; +} + +sub _join_version { + my ($self, @parts) = @_; + + return join ".", map { my $c = $_; $c =~ s/^0\./0/; $c } grep defined, @parts; +} +sub _split_version { + my ($self, $str) = @_; + my @version_parts = map { s/^0(?=\d)/0./; $_ } $str =~ m/(\d+)/g; + return @version_parts[0..2]; +} + +sub normalized_version { + my ( $self ) = @_; + my @version_parts = map { $_ || 0 } $self->_split_version( $self->version ); + my $result = sprintf('%d%02d%02d', @version_parts); + PTDEBUG && _d($self->version, 'normalizes to', $result); + return $result; +} + +sub comment { + my ( $self, $cmd ) = @_; + my $v = $self->normalized_version(); + + return "/*!$v $cmd */" +} + +my @methods = qw(major minor revision); +sub cmp { + my ($left, $right) = @_; + my $right_obj = (blessed($right) && $right->isa(ref($left))) + ? $right + : ref($left)->new($right); + + my $retval = 0; + for my $m ( @methods ) { + last unless defined($left->$m) && defined($right_obj->$m); + $retval = $left->$m <=> $right_obj->$m; + last if $retval; + } + return $retval; +} + +sub BUILDARGS { + my $self = shift; + + if ( @_ == 1 ) { + my %args; + if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { + my $dbh = $_[0]; + my $query; + PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); + if ( eval { $query = $dbh->selectall_hashref(q) } ) { + @args{@methods} = $self->_split_version($query->{version}); + $args{flavor} = delete $query->{version_comment} + if $query->{version_comment}; + } + elsif ( eval { ($query) = $dbh->selectrow_array('SELECT VERSION()') } ) { + @args{@methods} = $self->_split_version($query); + } + else { + PTDEBUG && _d("Couldn't get the version from the dbh: $@"); + } + $args{innodb_version} = eval { $self->_innodb_version($dbh) }; + } + elsif ( !ref($_[0]) ) { + @args{@methods} = $self->_split_version($_[0]); + } + + for my $method (@methods) { + delete $args{$method} unless defined $args{$method}; + } + @_ = %args if %args; + } + + return $self->SUPER::BUILDARGS(@_); +} + +sub _innodb_version { my ( $self, $dbh ) = @_; return unless $dbh; my $innodb_version = "NO"; @@ -1651,6 +2142,7 @@ sub _d { print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; } +no Mo; 1; } # ########################################################################### @@ -2080,7 +2572,7 @@ sub get_keys { my ( $type, $cols ) = $key =~ m/(?:USING (\w+))? \((.+)\)/; my ( $special ) = $key =~ m/(FULLTEXT|SPATIAL)/; $type = $type || $special || 'BTREE'; - if ( $opts->{mysql_version} && $opts->{mysql_version} lt '004001000' + if ( $opts->{mysql_version} && $opts->{mysql_version} lt '4.1' && $engine =~ m/HEAP|MEMORY/i ) { $type = 'HASH'; # MySQL pre-4.1 supports only HASH indexes on HEAP @@ -3994,7 +4486,7 @@ our %ALGOS = ( sub new { my ( $class, %args ) = @_; - foreach my $arg ( qw(Quoter VersionParser) ) { + foreach my $arg ( qw(Quoter) ) { die "I need a $arg argument" unless defined $args{$arg}; } my $self = { %args }; @@ -4050,21 +4542,22 @@ sub get_crc_type { sub best_algorithm { my ( $self, %args ) = @_; my ( $alg, $dbh ) = @args{ qw(algorithm dbh) }; - my $vp = $self->{VersionParser}; my @choices = sort { $ALGOS{$a}->{pref} <=> $ALGOS{$b}->{pref} } keys %ALGOS; die "Invalid checksum algorithm $alg" if $alg && !$ALGOS{$alg}; + my $version = VersionParser->new($dbh); + if ( $args{where} || $args{chunk} # CHECKSUM does whole table || $args{replicate} # CHECKSUM can't do INSERT.. SELECT - || !$vp->version_ge($dbh, '4.1.1')) # CHECKSUM doesn't exist + || $version < '4.1.1') # CHECKSUM doesn't exist { PTDEBUG && _d('Cannot use CHECKSUM algorithm'); @choices = grep { $_ ne 'CHECKSUM' } @choices; } - if ( !$vp->version_ge($dbh, '4.1.1') ) { + if ( $version < '4.1.1' ) { PTDEBUG && _d('Cannot use BIT_XOR algorithm because MySQL < 4.1.1'); @choices = grep { $_ ne 'BIT_XOR' } @choices; } @@ -5191,7 +5684,7 @@ $Data::Dumper::Quotekeys = 0; sub new { my ( $class, %args ) = @_; - my @required_args = qw(MasterSlave Quoter VersionParser TableChecksum Retry); + my @required_args = qw(MasterSlave Quoter TableChecksum Retry); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless defined $args{$arg}; } @@ -5244,7 +5737,6 @@ sub sync_table { $args{timeout_ok} ||= 0; my $q = $self->{Quoter}; - my $vp = $self->{VersionParser}; my ($plugin, %plugin_args) = $self->get_best_plugin(%args); die "No plugin can sync $src->{db}.$src->{tbl}" unless $plugin; @@ -5256,8 +5748,8 @@ sub sync_table { PTDEBUG && _d('CRC column:', $crc_col); my $index_hint; - my $hint = ($vp->version_ge($src->{dbh}, '4.0.9') - && $vp->version_ge($dst->{dbh}, '4.0.9') ? 'FORCE' : 'USE') + my $hint = ((VersionParser->new($src->{dbh}) >= '4.0.9' + && VersionParser->new($dst->{dbh}) >= '4.0.9') ? 'FORCE' : 'USE') . ' INDEX'; if ( $args{chunk_index} ) { PTDEBUG && _d('Using given chunk index for index hint'); @@ -6158,8 +6650,7 @@ sub get_connected_slaves { my $show = "SHOW GRANTS FOR "; my $user = 'CURRENT_USER()'; - my $vp = $self->{VersionParser}; - if ( $vp && !$vp->version_ge($dbh, '4.1.2') ) { + if ( VersionParser->new($dbh) < '4.1.2' ) { $user = $dbh->selectrow_arrayref('SELECT USER()')->[0]; $user =~ s/([^@]+)@(.+)/'$1'\@'$2'/; } @@ -7739,7 +8230,6 @@ sub main { # ######################################################################## # Get configuration information. # ######################################################################## - my $vp = new VersionParser(); my $o = new OptionParser(); $o->get_specs(); $o->get_opts(); @@ -7850,15 +8340,14 @@ sub main { # Do the work. # ######################################################################## my $tp = new TableParser( Quoter => $q ); - my $ms = new MasterSlave(VersionParser => $vp); + my $ms = new MasterSlave(); my $du = new MySQLDump( cache => 0 ); my $rt = new Retry(); my $chunker = new TableChunker( Quoter => $q, TableParser => $tp ); my $nibbler = new TableNibbler( Quoter => $q, TableParser => $tp ); - my $checksum = new TableChecksum( Quoter => $q, VersionParser => $vp ); + my $checksum = new TableChecksum( Quoter => $q ); my $syncer = new TableSyncer( Quoter => $q, - VersionParser => $vp, MasterSlave => $ms, TableChecksum => $checksum, DSNParser => $dp, @@ -7870,7 +8359,6 @@ sub main { MySQLDump => $du, TableParser => $tp, Quoter => $q, - VersionParser => $vp, TableChunker => $chunker, TableNibbler => $nibbler, TableChecksum => $checksum, @@ -7974,8 +8462,7 @@ sub main { # Exit status sub lock_and_rename { my ( %args ) = @_; - my @required_args = qw(dsns plugins OptionParser DSNParser Quoter - VersionParser); + my @required_args = qw(dsns plugins OptionParser DSNParser Quoter ); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } @@ -8000,9 +8487,8 @@ 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 ) { + if ( grep { VersionParser->new($_->{dbh}) < '5.5' } $src, $dst ) { disconnect($src, $dst); die "--lock-and-rename requires MySQL 5.5 or later"; } @@ -8072,8 +8558,7 @@ sub lock_and_rename { # Exit status sub sync_one_table { my ( %args ) = @_; - my @required_args = qw(dsns plugins OptionParser DSNParser Quoter - VersionParser); + my @required_args = qw(dsns plugins OptionParser DSNParser Quoter); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } @@ -8140,7 +8625,6 @@ sub sync_one_table { # OptionParser - object # DSNParser - object # Quoter - object -# VersionParser - object # TableChecksum - object # MasterSlave - object # @@ -8152,7 +8636,7 @@ sub sync_one_table { sub sync_via_replication { my ( %args ) = @_; my @required_args = qw(dsns plugins OptionParser DSNParser Quoter - VersionParser TableChecksum MasterSlave); + TableChecksum MasterSlave); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } @@ -8352,7 +8836,6 @@ sub sync_via_replication { # OptionParser - object # DSNParser - object # Quoter - object -# VersionParser - object # TableParser - object # MySQLDump - object # @@ -8889,8 +9372,7 @@ sub get_cxn { # instead, it should check if it's already set to STATEMENT. # This is becase starting with MySQL 5.1.29, changing the format # requires a SUPER user. - my $vp = new VersionParser(); - if ( $vp->version_ge($dbh, '5.1.5') ) { + if ( VersionParser->new($dbh) >= '5.1.29' ) { $sql = 'SELECT @@binlog_format'; PTDEBUG && _d($dbh, $sql); my ($original_binlog_format) = $dbh->selectrow_array($sql); @@ -8944,7 +9426,6 @@ sub get_cxn { # dst - Hashref with destination host information # DSNParser - object # Quoter - object -# VersionParser - object # TableParser - object # MySQLDump - object # TableSyncer - object @@ -8954,12 +9435,12 @@ sub get_cxn { # Table structure (from ) if ok to sync, else it dies. sub ok_to_sync { my ( %args ) = @_; - my @required_args = qw(src dst DSNParser Quoter VersionParser TableParser + my @required_args = qw(src dst DSNParser Quoter TableParser MySQLDump TableSyncer OptionParser); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } - my ($src, $dst, $dp, $q, $vp, $tp, $du, $syncer, $o) = @args{@required_args}; + my ($src, $dst, $dp, $q, $tp, $du, $syncer, $o) = @args{@required_args}; if ( !$src->{tbl_struct} ) { eval { @@ -9005,7 +9486,7 @@ sub ok_to_sync { if ( $o->get('check-triggers') ) { PTDEBUG && _d('Checking for triggers'); if ( !defined $dst->{supports_triggers} ) { - $dst->{supports_triggers} = $vp->version_ge($dst->{dbh}, '5.0.2'); + $dst->{supports_triggers} = VersionParser->new($dst->{dbh}) >= '5.0.2'; } if ( $dst->{supports_triggers} && $du->get_triggers($dst->{dbh}, $q, $dst->{db}, $dst->{tbl}) ) { diff --git a/bin/pt-upgrade b/bin/pt-upgrade index 908d4495..b1cb2986 100755 --- a/bin/pt-upgrade +++ b/bin/pt-upgrade @@ -673,7 +673,7 @@ sub get_keys { my ( $type, $cols ) = $key =~ m/(?:USING (\w+))? \((.+)\)/; my ( $special ) = $key =~ m/(FULLTEXT|SPATIAL)/; $type = $type || $special || 'BTREE'; - if ( $opts->{mysql_version} && $opts->{mysql_version} lt '004001000' + if ( $opts->{mysql_version} && $opts->{mysql_version} lt '4.1' && $engine =~ m/HEAP|MEMORY/i ) { $type = 'HASH'; # MySQL pre-4.1 supports only HASH indexes on HEAP @@ -1960,6 +1960,456 @@ if ( PTDEBUG ) { # End OptionParser package # ########################################################################### +# ########################################################################### +# Mo 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/Mo.pm +# t/lib/Mo.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +BEGIN { +$INC{"Mo.pm"} = __FILE__; +package Mo; +our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. + +{ + no strict 'refs'; + sub _glob_for { + return \*{shift()} + } + + sub _stash_for { + return \%{ shift() . "::" }; + } +} + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util (); + +our %TYPES = ( + Bool => sub { !$_[0] || (defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == 1) }, + Num => sub { defined $_[0] && &Scalar::Util::looks_like_number }, + Int => sub { defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == int $_[0] }, + Str => sub { defined $_[0] }, + Object => sub { defined $_[0] && &Scalar::Util::blessed }, + FileHandle => sub { local $@; require IO::Handle; fileno($_[0]) && $_[0]->opened }, + + map { + my $type = /R/ ? $_ : uc $_; + $_ . "Ref" => sub { ref $_[0] eq $type } + } qw(Array Code Hash Regexp Glob Scalar) +); + +our %metadata_for; +{ + package Mo::Object; + + sub new { + my $class = shift; + my $args = $class->BUILDARGS(@_); + + my @args_to_delete; + while ( my ($attr, $meta) = each %{$metadata_for{$class}} ) { + next unless exists $meta->{init_arg}; + my $init_arg = $meta->{init_arg}; + + if ( defined $init_arg ) { + $args->{$attr} = delete $args->{$init_arg}; + } + else { + push @args_to_delete, $attr; + } + } + + delete $args->{$_} for @args_to_delete; + + for my $attribute ( keys %$args ) { + if ( my $coerce = $metadata_for{$class}{$attribute}{coerce} ) { + $args->{$attribute} = $coerce->($args->{$attribute}); + } + if ( my $I = $metadata_for{$class}{$attribute}{isa} ) { + ( (my $I_name), $I ) = @{$I}; + Mo::_check_type_constaints($attribute, $I, $I_name, $args->{$attribute}); + } + } + + while ( my ($attribute, $meta) = each %{$metadata_for{$class}} ) { + next unless $meta->{required}; + Carp::confess("Attribute ($attribute) is required for $class") + if ! exists $args->{$attribute} + } + + @_ = %$args; + my $self = bless $args, $class; + + my @build_subs; + my $linearized_isa = mro::get_linear_isa($class); + + for my $isa_class ( @$linearized_isa ) { + unshift @build_subs, *{ Mo::_glob_for "${isa_class}::BUILD" }{CODE}; + } + exists &$_ && $_->( $self, @_ ) for grep { defined } @build_subs; + return $self; + } + + sub BUILDARGS { + shift; + my $ref; + if ( @_ == 1 && ref($_[0]) ) { + Carp::confess("Single parameters to new() must be a HASH ref") + unless ref($_[0]) eq ref({}); + $ref = {%{$_[0]}} # We want a new reference, always + } + else { + $ref = { @_ }; + } + return $ref; + } +} + +my %export_for; +sub Mo::import { + warnings->import(qw(FATAL all)); + strict->import(); + + my $caller = scalar caller(); # Caller's package + my $caller_pkg = $caller . "::"; # Caller's package with :: at the end + my (%exports, %options); + + my (undef, @features) = @_; + my %ignore = ( map { $_ => 1 } qw( is isa init_arg builder buildargs clearer predicate build handles default required ) ); + for my $feature (grep { !$ignore{$_} } @features) { + { local $@; require "Mo/$feature.pm"; } + { + no strict 'refs'; + &{"Mo::${feature}::e"}( + $caller_pkg, + \%exports, + \%options, + \@_ + ); + } + } + + return if $exports{M}; + + %exports = ( + extends => sub { + for my $class ( map { "$_" } @_ ) { + $class =~ s{::|'}{/}g; + { local $@; eval { require "$class.pm" } } # or warn $@; + } + _set_package_isa($caller, @_); + _set_inherited_metadata($caller); + }, + has => sub { + my $names = shift; + for my $attribute ( ref $names ? @$names : $names ) { + my %args = @_; + my $method = ($args{is} || '') eq 'ro' + ? sub { + Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller_pkg}${attribute}") + if $#_; + return $_[0]{$attribute}; + } + : sub { + return $#_ + ? $_[0]{$attribute} = $_[1] + : $_[0]{$attribute}; + }; + + $metadata_for{$caller}{$attribute} = (); + + if ( my $I = $args{isa} ) { + my $orig_I = $I; + my $type; + if ( $I =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $I = _nested_constraints($attribute, $1, $2); + } + $metadata_for{$caller}{$attribute}{isa} = [$orig_I, $I]; + my $orig_method = $method; + $method = sub { + if ( $#_ ) { + Mo::_check_type_constaints($attribute, $I, $orig_I, $_[1]); + } + goto &$orig_method; + }; + } + + if ( my $builder = $args{builder} ) { + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$builder + : goto &$original_method + }; + } + + if ( my $code = $args{default} ) { + Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") + unless ref($code) eq 'CODE'; + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$code + : goto &$original_method + }; + } + + if ( my $role = $args{does} ) { + my $original_method = $method; + $method = sub { + if ( $#_ ) { + Carp::confess(qq) + unless blessed($_[1]) && $_[1]->does($role) + } + goto &$original_method + }; + } + + if ( my $coercion = $args{coerce} ) { + $metadata_for{$caller}{$attribute}{coerce} = $coercion; + my $original_method = $method; + $method = sub { + if ( $#_ ) { + return $original_method->($_[0], $coercion->($_[1])) + } + goto &$original_method; + } + } + + $method = $options{$_}->($method, $attribute, @_) + for sort keys %options; + + *{ _glob_for "${caller}::$attribute" } = $method; + + if ( $args{required} ) { + $metadata_for{$caller}{$attribute}{required} = 1; + } + + if ($args{clearer}) { + *{ _glob_for "${caller}::$args{clearer}" } + = sub { delete shift->{$attribute} } + } + + if ($args{predicate}) { + *{ _glob_for "${caller}::$args{predicate}" } + = sub { exists shift->{$attribute} } + } + + if ($args{handles}) { + _has_handles($caller, $attribute, \%args); + } + + if (exists $args{init_arg}) { + $metadata_for{$caller}{$attribute}{init_arg} = $args{init_arg}; + } + } + }, + %exports, + ); + + $export_for{$caller} = [ keys %exports ]; + + for my $keyword ( keys %exports ) { + *{ _glob_for "${caller}::$keyword" } = $exports{$keyword} + } + *{ _glob_for "${caller}::extends" }{CODE}->( "Mo::Object" ) + unless @{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] }; +}; + +sub _check_type_constaints { + my ($attribute, $I, $I_name, $val) = @_; + ( ref($I) eq 'CODE' + ? $I->($val) + : (ref $val eq $I + || ($val && $val eq $I) + || (exists $TYPES{$I} && $TYPES{$I}->($val))) + ) + || Carp::confess( + qq + . qq + . (defined $val ? Mo::Dumper($val) : 'undef') ) +} + +sub _has_handles { + my ($caller, $attribute, $args) = @_; + my $handles = $args->{handles}; + + my $ref = ref $handles; + my $kv; + if ( $ref eq ref [] ) { + $kv = { map { $_,$_ } @{$handles} }; + } + elsif ( $ref eq ref {} ) { + $kv = $handles; + } + elsif ( $ref eq ref qr// ) { + Carp::confess("Cannot delegate methods based on a Regexp without a type constraint (isa)") + unless $args->{isa}; + my $target_class = $args->{isa}; + $kv = { + map { $_, $_ } + grep { $_ =~ $handles } + grep { !exists $Mo::Object::{$_} && $target_class->can($_) } + grep { $_ ne 'has' && $_ ne 'extends' } + keys %{ _stash_for $target_class } + }; + } + else { + Carp::confess("handles for $ref not yet implemented"); + } + + while ( my ($method, $target) = each %{$kv} ) { + my $name = _glob_for "${caller}::$method"; + Carp::confess("You cannot overwrite a locally defined method ($method) with a delegation") + if defined &$name; + + my ($target, @curried_args) = ref($target) ? @$target : $target; + *$name = sub { + my $self = shift; + my $delegate_to = $self->$attribute(); + my $error = "Cannot delegate $method to $target because the value of $attribute"; + Carp::confess("$error is not defined") unless $delegate_to; + Carp::confess("$error is not an object (got '$delegate_to')") + unless Scalar::Util::blessed($delegate_to) || (!ref($delegate_to) && $delegate_to->can($target)); + return $delegate_to->$target(@curried_args, @_); + } + } +} + +sub _nested_constraints { + my ($attribute, $aggregate_type, $type) = @_; + + my $inner_types; + if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $inner_types = _nested_constraints($1, $2); + } + else { + $inner_types = $TYPES{$type}; + } + + if ( $aggregate_type eq 'ArrayRef' ) { + return sub { + my ($val) = @_; + return unless ref($val) eq ref([]); + + if ($inner_types) { + for my $value ( @{$val} ) { + return unless $inner_types->($value) + } + } + else { + for my $value ( @{$val} ) { + return unless $value && ($value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type))); + } + } + return 1; + }; + } + elsif ( $aggregate_type eq 'Maybe' ) { + return sub { + my ($value) = @_; + return 1 if ! defined($value); + if ($inner_types) { + return unless $inner_types->($value) + } + else { + return unless $value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type)); + } + return 1; + } + } + else { + Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); + } +} + +sub _set_package_isa { + my ($package, @new_isa) = @_; + + *{ _glob_for "${package}::ISA" } = [@new_isa]; +} + +sub _set_inherited_metadata { + my $class = shift; + my $linearized_isa = mro::get_linear_isa($class); + my %new_metadata; + + for my $isa_class (reverse @$linearized_isa) { + %new_metadata = ( + %new_metadata, + %{ $metadata_for{$isa_class} || {} }, + ); + } + $metadata_for{$class} = \%new_metadata; +} + +sub unimport { + my $caller = scalar caller(); + my $stash = _stash_for( $caller ); + + delete $stash->{$_} for @{$export_for{$caller}}; +} + +sub Dumper { + require Data::Dumper; + local $Data::Dumper::Indent = 0; + local $Data::Dumper::Sortkeys = 0; + local $Data::Dumper::Quotekeys = 0; + local $Data::Dumper::Terse = 1; + + Data::Dumper::Dumper(@_) +} + +BEGIN { + if ($] >= 5.010) { + { local $@; require mro; } + } + else { + local $@; + eval { + require MRO::Compat; + } or do { + *mro::get_linear_isa = *mro::get_linear_isa_dfs = sub { + no strict 'refs'; + + my $classname = shift; + + my @lin = ($classname); + my %stored; + foreach my $parent (@{"$classname\::ISA"}) { + my $plin = mro::get_linear_isa_dfs($parent); + foreach (@$plin) { + next if exists $stored{$_}; + push(@lin, $_); + $stored{$_} = 1; + } + } + return \@lin; + }; + } + } +} + +} +1; +} +# ########################################################################### +# End Mo package +# ########################################################################### + # ########################################################################### # Transformers package # This package is a copy without comments from the original. The original @@ -6176,7 +6626,7 @@ our %ALGOS = ( sub new { my ( $class, %args ) = @_; - foreach my $arg ( qw(Quoter VersionParser) ) { + foreach my $arg ( qw(Quoter) ) { die "I need a $arg argument" unless defined $args{$arg}; } my $self = { %args }; @@ -6232,21 +6682,22 @@ sub get_crc_type { sub best_algorithm { my ( $self, %args ) = @_; my ( $alg, $dbh ) = @args{ qw(algorithm dbh) }; - my $vp = $self->{VersionParser}; my @choices = sort { $ALGOS{$a}->{pref} <=> $ALGOS{$b}->{pref} } keys %ALGOS; die "Invalid checksum algorithm $alg" if $alg && !$ALGOS{$alg}; + my $version = VersionParser->new($dbh); + if ( $args{where} || $args{chunk} # CHECKSUM does whole table || $args{replicate} # CHECKSUM can't do INSERT.. SELECT - || !$vp->version_ge($dbh, '4.1.1')) # CHECKSUM doesn't exist + || $version < '4.1.1') # CHECKSUM doesn't exist { PTDEBUG && _d('Cannot use CHECKSUM algorithm'); @choices = grep { $_ ne 'CHECKSUM' } @choices; } - if ( !$vp->version_ge($dbh, '4.1.1') ) { + if ( $version < '4.1.1' ) { PTDEBUG && _d('Cannot use BIT_XOR algorithm because MySQL < 4.1.1'); @choices = grep { $_ ne 'BIT_XOR' } @choices; } @@ -6553,7 +7004,7 @@ $Data::Dumper::Quotekeys = 0; sub new { my ( $class, %args ) = @_; - my @required_args = qw(MasterSlave Quoter VersionParser TableChecksum Retry); + my @required_args = qw(MasterSlave Quoter TableChecksum Retry); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless defined $args{$arg}; } @@ -6606,7 +7057,6 @@ sub sync_table { $args{timeout_ok} ||= 0; my $q = $self->{Quoter}; - my $vp = $self->{VersionParser}; my ($plugin, %plugin_args) = $self->get_best_plugin(%args); die "No plugin can sync $src->{db}.$src->{tbl}" unless $plugin; @@ -6618,8 +7068,8 @@ sub sync_table { PTDEBUG && _d('CRC column:', $crc_col); my $index_hint; - my $hint = ($vp->version_ge($src->{dbh}, '4.0.9') - && $vp->version_ge($dst->{dbh}, '4.0.9') ? 'FORCE' : 'USE') + my $hint = ((VersionParser->new($src->{dbh}) >= '4.0.9' + && VersionParser->new($dst->{dbh}) >= '4.0.9') ? 'FORCE' : 'USE') . ' INDEX'; if ( $args{chunk_index} ) { PTDEBUG && _d('Using given chunk index for index hint'); @@ -8121,96 +8571,137 @@ sub _d { { package VersionParser; -use strict; -use warnings FATAL => 'all'; +use Mo; +use Scalar::Util qw(blessed); use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; -sub new { - my ( $class ) = @_; - bless {}, $class; -} +use overload ( + '""' => "version", + '<=>' => "cmp", + 'cmp' => "cmp", + fallback => 1, +); -sub parse { - my ( $self, $str ) = @_; - 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; -} +our $VERSION = 0.01; -sub version_cmp { - my ($self, $dbh, $target, $cmp) = @_; - my $version = $self->version($dbh); - my $result; +has major => ( + is => 'ro', + isa => 'Int', + required => 1, +); - 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" - } +has [qw( minor revision )] => ( + is => 'ro', + isa => 'Num', +); - PTDEBUG && _d($self->{$dbh}, $cmp, $target, ':', $result); - return $result; -} +has flavor => ( + is => 'ro', + isa => 'Str', + default => sub { 'Unknown' }, +); -sub version_ge { - my ( $self, $dbh, $target ) = @_; - return $self->version_cmp($dbh, $target, 'ge'); -} +has innodb_version => ( + is => 'ro', + isa => 'Str', + default => sub { 'NO' }, +); -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 series { + my $self = shift; + return $self->_join_version($self->major, $self->minor); } sub version { - my ( $self, $dbh ) = @_; - if ( !$self->{$dbh} ) { - $self->{$dbh} = $self->parse( - $dbh->selectrow_array('SELECT VERSION()')); - } - return $self->{$dbh}; + my $self = shift; + return $self->_join_version($self->major, $self->minor, $self->revision); } -sub innodb_version { +sub is_in { + my ($self, $target) = @_; + + return $self eq $target; +} + +sub _join_version { + my ($self, @parts) = @_; + + return join ".", map { my $c = $_; $c =~ s/^0\./0/; $c } grep defined, @parts; +} +sub _split_version { + my ($self, $str) = @_; + my @version_parts = map { s/^0(?=\d)/0./; $_ } $str =~ m/(\d+)/g; + return @version_parts[0..2]; +} + +sub normalized_version { + my ( $self ) = @_; + my @version_parts = map { $_ || 0 } $self->_split_version( $self->version ); + my $result = sprintf('%d%02d%02d', @version_parts); + PTDEBUG && _d($self->version, 'normalizes to', $result); + return $result; +} + +sub comment { + my ( $self, $cmd ) = @_; + my $v = $self->normalized_version(); + + return "/*!$v $cmd */" +} + +my @methods = qw(major minor revision); +sub cmp { + my ($left, $right) = @_; + my $right_obj = (blessed($right) && $right->isa(ref($left))) + ? $right + : ref($left)->new($right); + + my $retval = 0; + for my $m ( @methods ) { + last unless defined($left->$m) && defined($right_obj->$m); + $retval = $left->$m <=> $right_obj->$m; + last if $retval; + } + return $retval; +} + +sub BUILDARGS { + my $self = shift; + + if ( @_ == 1 ) { + my %args; + if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { + my $dbh = $_[0]; + my $query; + PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); + if ( eval { $query = $dbh->selectall_hashref(q) } ) { + @args{@methods} = $self->_split_version($query->{version}); + $args{flavor} = delete $query->{version_comment} + if $query->{version_comment}; + } + elsif ( eval { ($query) = $dbh->selectrow_array('SELECT VERSION()') } ) { + @args{@methods} = $self->_split_version($query); + } + else { + PTDEBUG && _d("Couldn't get the version from the dbh: $@"); + } + $args{innodb_version} = eval { $self->_innodb_version($dbh) }; + } + elsif ( !ref($_[0]) ) { + @args{@methods} = $self->_split_version($_[0]); + } + + for my $method (@methods) { + delete $args{$method} unless defined $args{$method}; + } + @_ = %args if %args; + } + + return $self->SUPER::BUILDARGS(@_); +} + +sub _innodb_version { my ( $self, $dbh ) = @_; return unless $dbh; my $innodb_version = "NO"; @@ -8248,6 +8739,7 @@ sub _d { print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; } +no Mo; 1; } # ########################################################################### @@ -10502,18 +10994,16 @@ sub main { # Make some common modules. # ######################################################################## my $q = new Quoter(); - my $vp = new VersionParser(); my $qp = new QueryParser(); my $qr = new QueryRewriter(); my $rr = new Retry(); my $tp = new TableParser(Quoter => $q); my $chunker = new TableChunker(Quoter => $q, TableParser => $tp ); my $nibbler = new TableNibbler(Quoter => $q, TableParser => $tp ); - my $checksum = new TableChecksum(Quoter => $q, VersionParser => $vp); + my $checksum = new TableChecksum(Quoter => $q); my $syncer = new TableSyncer( MasterSlave => 1, # I don't think we need this. Quoter => $q, - VersionParser => $vp, TableChecksum => $checksum, Retry => $rr, ); @@ -10524,7 +11014,6 @@ sub main { QueryRewriter => $qr, TableParser => $tp, Quoter => $q, - VersionParser => $vp, TableChunker => $chunker, TableNibbler => $nibbler, TableChecksum => $checksum, @@ -10567,7 +11056,7 @@ sub main { # SHOW WARNINGS requires MySQL 4.1. my $have_warnings = 1; foreach my $host ( @$hosts ) { - if ( !$vp->version_ge($host->{dbh}, '4.1.0') ) { + if ( VersionParser->new($host->{dbh}) < '4.1.0' ) { warn "Compare warnings DISABLED because host ", $host->{name}, " MySQL version is less than 4.1"; $have_warnings = 0; diff --git a/bin/pt-variable-advisor b/bin/pt-variable-advisor index de54ac08..270b6c28 100755 --- a/bin/pt-variable-advisor +++ b/bin/pt-variable-advisor @@ -1033,6 +1033,456 @@ if ( PTDEBUG ) { # End OptionParser package # ########################################################################### +# ########################################################################### +# Mo 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/Mo.pm +# t/lib/Mo.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +BEGIN { +$INC{"Mo.pm"} = __FILE__; +package Mo; +our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. + +{ + no strict 'refs'; + sub _glob_for { + return \*{shift()} + } + + sub _stash_for { + return \%{ shift() . "::" }; + } +} + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util (); + +our %TYPES = ( + Bool => sub { !$_[0] || (defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == 1) }, + Num => sub { defined $_[0] && &Scalar::Util::looks_like_number }, + Int => sub { defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == int $_[0] }, + Str => sub { defined $_[0] }, + Object => sub { defined $_[0] && &Scalar::Util::blessed }, + FileHandle => sub { local $@; require IO::Handle; fileno($_[0]) && $_[0]->opened }, + + map { + my $type = /R/ ? $_ : uc $_; + $_ . "Ref" => sub { ref $_[0] eq $type } + } qw(Array Code Hash Regexp Glob Scalar) +); + +our %metadata_for; +{ + package Mo::Object; + + sub new { + my $class = shift; + my $args = $class->BUILDARGS(@_); + + my @args_to_delete; + while ( my ($attr, $meta) = each %{$metadata_for{$class}} ) { + next unless exists $meta->{init_arg}; + my $init_arg = $meta->{init_arg}; + + if ( defined $init_arg ) { + $args->{$attr} = delete $args->{$init_arg}; + } + else { + push @args_to_delete, $attr; + } + } + + delete $args->{$_} for @args_to_delete; + + for my $attribute ( keys %$args ) { + if ( my $coerce = $metadata_for{$class}{$attribute}{coerce} ) { + $args->{$attribute} = $coerce->($args->{$attribute}); + } + if ( my $I = $metadata_for{$class}{$attribute}{isa} ) { + ( (my $I_name), $I ) = @{$I}; + Mo::_check_type_constaints($attribute, $I, $I_name, $args->{$attribute}); + } + } + + while ( my ($attribute, $meta) = each %{$metadata_for{$class}} ) { + next unless $meta->{required}; + Carp::confess("Attribute ($attribute) is required for $class") + if ! exists $args->{$attribute} + } + + @_ = %$args; + my $self = bless $args, $class; + + my @build_subs; + my $linearized_isa = mro::get_linear_isa($class); + + for my $isa_class ( @$linearized_isa ) { + unshift @build_subs, *{ Mo::_glob_for "${isa_class}::BUILD" }{CODE}; + } + exists &$_ && $_->( $self, @_ ) for grep { defined } @build_subs; + return $self; + } + + sub BUILDARGS { + shift; + my $ref; + if ( @_ == 1 && ref($_[0]) ) { + Carp::confess("Single parameters to new() must be a HASH ref") + unless ref($_[0]) eq ref({}); + $ref = {%{$_[0]}} # We want a new reference, always + } + else { + $ref = { @_ }; + } + return $ref; + } +} + +my %export_for; +sub Mo::import { + warnings->import(qw(FATAL all)); + strict->import(); + + my $caller = scalar caller(); # Caller's package + my $caller_pkg = $caller . "::"; # Caller's package with :: at the end + my (%exports, %options); + + my (undef, @features) = @_; + my %ignore = ( map { $_ => 1 } qw( is isa init_arg builder buildargs clearer predicate build handles default required ) ); + for my $feature (grep { !$ignore{$_} } @features) { + { local $@; require "Mo/$feature.pm"; } + { + no strict 'refs'; + &{"Mo::${feature}::e"}( + $caller_pkg, + \%exports, + \%options, + \@_ + ); + } + } + + return if $exports{M}; + + %exports = ( + extends => sub { + for my $class ( map { "$_" } @_ ) { + $class =~ s{::|'}{/}g; + { local $@; eval { require "$class.pm" } } # or warn $@; + } + _set_package_isa($caller, @_); + _set_inherited_metadata($caller); + }, + has => sub { + my $names = shift; + for my $attribute ( ref $names ? @$names : $names ) { + my %args = @_; + my $method = ($args{is} || '') eq 'ro' + ? sub { + Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller_pkg}${attribute}") + if $#_; + return $_[0]{$attribute}; + } + : sub { + return $#_ + ? $_[0]{$attribute} = $_[1] + : $_[0]{$attribute}; + }; + + $metadata_for{$caller}{$attribute} = (); + + if ( my $I = $args{isa} ) { + my $orig_I = $I; + my $type; + if ( $I =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $I = _nested_constraints($attribute, $1, $2); + } + $metadata_for{$caller}{$attribute}{isa} = [$orig_I, $I]; + my $orig_method = $method; + $method = sub { + if ( $#_ ) { + Mo::_check_type_constaints($attribute, $I, $orig_I, $_[1]); + } + goto &$orig_method; + }; + } + + if ( my $builder = $args{builder} ) { + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$builder + : goto &$original_method + }; + } + + if ( my $code = $args{default} ) { + Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") + unless ref($code) eq 'CODE'; + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$code + : goto &$original_method + }; + } + + if ( my $role = $args{does} ) { + my $original_method = $method; + $method = sub { + if ( $#_ ) { + Carp::confess(qq) + unless blessed($_[1]) && $_[1]->does($role) + } + goto &$original_method + }; + } + + if ( my $coercion = $args{coerce} ) { + $metadata_for{$caller}{$attribute}{coerce} = $coercion; + my $original_method = $method; + $method = sub { + if ( $#_ ) { + return $original_method->($_[0], $coercion->($_[1])) + } + goto &$original_method; + } + } + + $method = $options{$_}->($method, $attribute, @_) + for sort keys %options; + + *{ _glob_for "${caller}::$attribute" } = $method; + + if ( $args{required} ) { + $metadata_for{$caller}{$attribute}{required} = 1; + } + + if ($args{clearer}) { + *{ _glob_for "${caller}::$args{clearer}" } + = sub { delete shift->{$attribute} } + } + + if ($args{predicate}) { + *{ _glob_for "${caller}::$args{predicate}" } + = sub { exists shift->{$attribute} } + } + + if ($args{handles}) { + _has_handles($caller, $attribute, \%args); + } + + if (exists $args{init_arg}) { + $metadata_for{$caller}{$attribute}{init_arg} = $args{init_arg}; + } + } + }, + %exports, + ); + + $export_for{$caller} = [ keys %exports ]; + + for my $keyword ( keys %exports ) { + *{ _glob_for "${caller}::$keyword" } = $exports{$keyword} + } + *{ _glob_for "${caller}::extends" }{CODE}->( "Mo::Object" ) + unless @{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] }; +}; + +sub _check_type_constaints { + my ($attribute, $I, $I_name, $val) = @_; + ( ref($I) eq 'CODE' + ? $I->($val) + : (ref $val eq $I + || ($val && $val eq $I) + || (exists $TYPES{$I} && $TYPES{$I}->($val))) + ) + || Carp::confess( + qq + . qq + . (defined $val ? Mo::Dumper($val) : 'undef') ) +} + +sub _has_handles { + my ($caller, $attribute, $args) = @_; + my $handles = $args->{handles}; + + my $ref = ref $handles; + my $kv; + if ( $ref eq ref [] ) { + $kv = { map { $_,$_ } @{$handles} }; + } + elsif ( $ref eq ref {} ) { + $kv = $handles; + } + elsif ( $ref eq ref qr// ) { + Carp::confess("Cannot delegate methods based on a Regexp without a type constraint (isa)") + unless $args->{isa}; + my $target_class = $args->{isa}; + $kv = { + map { $_, $_ } + grep { $_ =~ $handles } + grep { !exists $Mo::Object::{$_} && $target_class->can($_) } + grep { $_ ne 'has' && $_ ne 'extends' } + keys %{ _stash_for $target_class } + }; + } + else { + Carp::confess("handles for $ref not yet implemented"); + } + + while ( my ($method, $target) = each %{$kv} ) { + my $name = _glob_for "${caller}::$method"; + Carp::confess("You cannot overwrite a locally defined method ($method) with a delegation") + if defined &$name; + + my ($target, @curried_args) = ref($target) ? @$target : $target; + *$name = sub { + my $self = shift; + my $delegate_to = $self->$attribute(); + my $error = "Cannot delegate $method to $target because the value of $attribute"; + Carp::confess("$error is not defined") unless $delegate_to; + Carp::confess("$error is not an object (got '$delegate_to')") + unless Scalar::Util::blessed($delegate_to) || (!ref($delegate_to) && $delegate_to->can($target)); + return $delegate_to->$target(@curried_args, @_); + } + } +} + +sub _nested_constraints { + my ($attribute, $aggregate_type, $type) = @_; + + my $inner_types; + if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $inner_types = _nested_constraints($1, $2); + } + else { + $inner_types = $TYPES{$type}; + } + + if ( $aggregate_type eq 'ArrayRef' ) { + return sub { + my ($val) = @_; + return unless ref($val) eq ref([]); + + if ($inner_types) { + for my $value ( @{$val} ) { + return unless $inner_types->($value) + } + } + else { + for my $value ( @{$val} ) { + return unless $value && ($value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type))); + } + } + return 1; + }; + } + elsif ( $aggregate_type eq 'Maybe' ) { + return sub { + my ($value) = @_; + return 1 if ! defined($value); + if ($inner_types) { + return unless $inner_types->($value) + } + else { + return unless $value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type)); + } + return 1; + } + } + else { + Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); + } +} + +sub _set_package_isa { + my ($package, @new_isa) = @_; + + *{ _glob_for "${package}::ISA" } = [@new_isa]; +} + +sub _set_inherited_metadata { + my $class = shift; + my $linearized_isa = mro::get_linear_isa($class); + my %new_metadata; + + for my $isa_class (reverse @$linearized_isa) { + %new_metadata = ( + %new_metadata, + %{ $metadata_for{$isa_class} || {} }, + ); + } + $metadata_for{$class} = \%new_metadata; +} + +sub unimport { + my $caller = scalar caller(); + my $stash = _stash_for( $caller ); + + delete $stash->{$_} for @{$export_for{$caller}}; +} + +sub Dumper { + require Data::Dumper; + local $Data::Dumper::Indent = 0; + local $Data::Dumper::Sortkeys = 0; + local $Data::Dumper::Quotekeys = 0; + local $Data::Dumper::Terse = 1; + + Data::Dumper::Dumper(@_) +} + +BEGIN { + if ($] >= 5.010) { + { local $@; require mro; } + } + else { + local $@; + eval { + require MRO::Compat; + } or do { + *mro::get_linear_isa = *mro::get_linear_isa_dfs = sub { + no strict 'refs'; + + my $classname = shift; + + my @lin = ($classname); + my %stored; + foreach my $parent (@{"$classname\::ISA"}) { + my $plin = mro::get_linear_isa_dfs($parent); + foreach (@$plin) { + next if exists $stored{$_}; + push(@lin, $_); + $stored{$_} = 1; + } + } + return \@lin; + }; + } + } +} + +} +1; +} +# ########################################################################### +# End Mo package +# ########################################################################### + # ########################################################################### # DSNParser package # This package is a copy without comments from the original. The original @@ -1405,96 +1855,137 @@ sub _d { { package VersionParser; -use strict; -use warnings FATAL => 'all'; +use Mo; +use Scalar::Util qw(blessed); use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; -sub new { - my ( $class ) = @_; - bless {}, $class; -} +use overload ( + '""' => "version", + '<=>' => "cmp", + 'cmp' => "cmp", + fallback => 1, +); -sub parse { - my ( $self, $str ) = @_; - 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; -} +our $VERSION = 0.01; -sub version_cmp { - my ($self, $dbh, $target, $cmp) = @_; - my $version = $self->version($dbh); - my $result; +has major => ( + is => 'ro', + isa => 'Int', + required => 1, +); - 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" - } +has [qw( minor revision )] => ( + is => 'ro', + isa => 'Num', +); - PTDEBUG && _d($self->{$dbh}, $cmp, $target, ':', $result); - return $result; -} +has flavor => ( + is => 'ro', + isa => 'Str', + default => sub { 'Unknown' }, +); -sub version_ge { - my ( $self, $dbh, $target ) = @_; - return $self->version_cmp($dbh, $target, 'ge'); -} +has innodb_version => ( + is => 'ro', + isa => 'Str', + default => sub { 'NO' }, +); -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 series { + my $self = shift; + return $self->_join_version($self->major, $self->minor); } sub version { - my ( $self, $dbh ) = @_; - if ( !$self->{$dbh} ) { - $self->{$dbh} = $self->parse( - $dbh->selectrow_array('SELECT VERSION()')); - } - return $self->{$dbh}; + my $self = shift; + return $self->_join_version($self->major, $self->minor, $self->revision); } -sub innodb_version { +sub is_in { + my ($self, $target) = @_; + + return $self eq $target; +} + +sub _join_version { + my ($self, @parts) = @_; + + return join ".", map { my $c = $_; $c =~ s/^0\./0/; $c } grep defined, @parts; +} +sub _split_version { + my ($self, $str) = @_; + my @version_parts = map { s/^0(?=\d)/0./; $_ } $str =~ m/(\d+)/g; + return @version_parts[0..2]; +} + +sub normalized_version { + my ( $self ) = @_; + my @version_parts = map { $_ || 0 } $self->_split_version( $self->version ); + my $result = sprintf('%d%02d%02d', @version_parts); + PTDEBUG && _d($self->version, 'normalizes to', $result); + return $result; +} + +sub comment { + my ( $self, $cmd ) = @_; + my $v = $self->normalized_version(); + + return "/*!$v $cmd */" +} + +my @methods = qw(major minor revision); +sub cmp { + my ($left, $right) = @_; + my $right_obj = (blessed($right) && $right->isa(ref($left))) + ? $right + : ref($left)->new($right); + + my $retval = 0; + for my $m ( @methods ) { + last unless defined($left->$m) && defined($right_obj->$m); + $retval = $left->$m <=> $right_obj->$m; + last if $retval; + } + return $retval; +} + +sub BUILDARGS { + my $self = shift; + + if ( @_ == 1 ) { + my %args; + if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { + my $dbh = $_[0]; + my $query; + PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); + if ( eval { $query = $dbh->selectall_hashref(q) } ) { + @args{@methods} = $self->_split_version($query->{version}); + $args{flavor} = delete $query->{version_comment} + if $query->{version_comment}; + } + elsif ( eval { ($query) = $dbh->selectrow_array('SELECT VERSION()') } ) { + @args{@methods} = $self->_split_version($query); + } + else { + PTDEBUG && _d("Couldn't get the version from the dbh: $@"); + } + $args{innodb_version} = eval { $self->_innodb_version($dbh) }; + } + elsif ( !ref($_[0]) ) { + @args{@methods} = $self->_split_version($_[0]); + } + + for my $method (@methods) { + delete $args{$method} unless defined $args{$method}; + } + @_ = %args if %args; + } + + return $self->SUPER::BUILDARGS(@_); +} + +sub _innodb_version { my ( $self, $dbh ) = @_; return unless $dbh; my $innodb_version = "NO"; @@ -1532,6 +2023,7 @@ sub _d { print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; } +no Mo; 1; } # ########################################################################### @@ -2800,27 +3292,11 @@ sub get_rules { my ( %args ) = @_; my $mysql_version = $args{mysql_version}; return 0 unless $mysql_version; - my ($major, $minor, $patch) = $mysql_version =~ m/(\d{3})/g; - if ( $major eq '003' ) { - return $mysql_version lt '003023000' ? 1 : 0; # 3.23.x - } - elsif ( $major eq '004' ) { - return $mysql_version lt '004001020' ? 1 : 0; # 4.1.20 - } - elsif ( $major eq '005' ) { - if ( $minor eq '000' ) { - return $mysql_version lt '005000037' ? 1 : 0; # 5.0.37 - } - elsif ( $minor eq '001' ) { - return $mysql_version lt '005001030' ? 1 : 0; # 5.1.30 - } - else { - return 0; - } - } - else { - return 0; - } + return 1 if ($mysql_version eq '3' && $mysql_version lt '3.23') + || ($mysql_version eq '4' && $mysql_version lt '4.1.20') + || ($mysql_version eq '5.0' && $mysql_version lt '5.0.37') + || ($mysql_version eq '5.1' && $mysql_version lt '5.1.30'); + return 0; }, }, { @@ -2829,7 +3305,7 @@ sub get_rules { my ( %args ) = @_; my $mysql_version = $args{mysql_version}; return 0 unless $mysql_version; - return $mysql_version lt '005001000' ? 1 : 0; # 5.1.x + return $mysql_version lt '5.1' ? 1 : 0; # 5.1.x }, }, }; @@ -2956,13 +3432,11 @@ sub main { # ######################################################################### # Make common modules. # ######################################################################### - my $vp = new VersionParser(); my $trp = new TextResultSetParser(); my %common_modules = ( OptionParser => $o, DSNParser => $dp, TextResultSetParser => $trp, - VersionParser => $vp, ); # ########################################################################## @@ -3006,8 +3480,9 @@ sub main { %common_modules, ); - my $mysql_version = $vp->parse($vars->{version}); - my $innodb_version = $vp->innodb_version($dbh); + my $mysql_version = VersionParser->new($vars->{version}); + my $innodb_version = VersionParser->new($dbh)->innodb_version() + if $dbh; PTDEBUG && _d("MySQL version", $mysql_version, "InnoDB version", $innodb_version); From 8179b1f1a1d55a09396eb5e3d465871c18766539 Mon Sep 17 00:00:00 2001 From: Brian Fraser Date: Wed, 11 Jul 2012 15:21:47 -0300 Subject: [PATCH 07/27] Updated the tests to use the new VersionParser --- t/lib/CompareResults.t | 6 +- t/lib/DuplicateKeyFinder.t | 3 +- t/lib/MasterSlave.t | 3 +- t/lib/MockSyncStream.t | 2 - t/lib/TableChecksum.t | 12 +-- t/lib/TableSyncChunk.t | 4 +- t/lib/TableSyncNibble.t | 4 - t/lib/TableSyncer.t | 10 +-- t/lib/VariableAdvisorRules.t | 13 +-- t/lib/VersionParser.t | 84 ++++++++++++++++++-- t/pt-query-digest/execute.t | 2 - t/pt-query-digest/explain.t | 2 - t/pt-query-digest/explain_partitions.t | 3 +- t/pt-slave-find/pt-slave-find.t | 10 ++- t/pt-table-checksum/create_replicate_table.t | 1 - t/pt-table-checksum/filters.t | 1 - t/pt-table-sync/bidirectional.t | 1 - t/pt-table-sync/binlog_format.t | 5 +- t/pt-table-sync/check_privs.t | 1 - t/pt-table-sync/columns.t | 1 - t/pt-table-sync/filters.t | 1 - t/pt-table-sync/float_precision.t | 1 - t/pt-table-sync/force_index.t | 1 - t/pt-table-sync/lock_and_rename.t | 3 +- t/pt-table-sync/lock_level.t | 1 - t/pt-table-sync/specify_column_or_index.t | 1 - t/pt-table-sync/sync_to_differnt_db.t | 1 - t/pt-table-sync/triggers.t | 5 +- 28 files changed, 103 insertions(+), 79 deletions(-) diff --git a/t/lib/CompareResults.t b/t/lib/CompareResults.t index beadd749..fc760979 100644 --- a/t/lib/CompareResults.t +++ b/t/lib/CompareResults.t @@ -17,7 +17,6 @@ use DSNParser; use QueryParser; use TableSyncer; use TableChecksum; -use VersionParser; use TableSyncGroupBy; use MockSyncStream; use MockSth; @@ -48,22 +47,19 @@ else { Transformers->import(qw(make_checksum)); -my $vp = new VersionParser(); my $q = new Quoter(); my $qp = new QueryParser(); my $tp = new TableParser(Quoter => $q); -my $tc = new TableChecksum(Quoter => $q, VersionParser => $vp); +my $tc = new TableChecksum(Quoter => $q); my $of = new Outfile(); my $rr = new Retry(); my $ts = new TableSyncer( Quoter => $q, - VersionParser => $vp, TableChecksum => $tc, Retry => $rr, MasterSlave => 1, ); my %modules = ( - VersionParser => $vp, Quoter => $q, TableParser => $tp, TableSyncer => $ts, diff --git a/t/lib/DuplicateKeyFinder.t b/t/lib/DuplicateKeyFinder.t index a59f8f79..e39ff00d 100644 --- a/t/lib/DuplicateKeyFinder.t +++ b/t/lib/DuplicateKeyFinder.t @@ -11,6 +11,7 @@ use warnings FATAL => 'all'; use English qw(-no_match_vars); use Test::More tests => 38; +use VersionParser; use DuplicateKeyFinder; use Quoter; use TableParser; @@ -26,7 +27,7 @@ my $callback = sub { push @$dupes, $_[0]; }; -my $opt = { version => '004001000' }; +my $opt = { mysql_version => VersionParser->new('4.1.0') }; my $ddl; my $tbl; diff --git a/t/lib/MasterSlave.t b/t/lib/MasterSlave.t index 53993338..cd32b1e1 100644 --- a/t/lib/MasterSlave.t +++ b/t/lib/MasterSlave.t @@ -20,8 +20,7 @@ use Cxn; use Sandbox; use PerconaTest; -my $vp = new VersionParser(); -my $ms = new MasterSlave(VersionParser => $vp); +my $ms = new MasterSlave(); my $dp = new DSNParser(opts=>$dsn_opts); my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp); diff --git a/t/lib/MockSyncStream.t b/t/lib/MockSyncStream.t index b98999db..c90f7d9d 100644 --- a/t/lib/MockSyncStream.t +++ b/t/lib/MockSyncStream.t @@ -78,11 +78,9 @@ is_deeply( # Test online stuff, e.g. get_cols_and_struct(). # ############################################################################# use DSNParser; -use VersionParser; use Sandbox; my $dp = new DSNParser(opts=>$dsn_opts); my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp); -my $vp = new VersionParser; my $dbh = $sb->get_dbh_for('master'); SKIP: { diff --git a/t/lib/TableChecksum.t b/t/lib/TableChecksum.t index 7a6b6f6a..33f08d53 100644 --- a/t/lib/TableChecksum.t +++ b/t/lib/TableChecksum.t @@ -11,8 +11,8 @@ use warnings FATAL => 'all'; use English qw(-no_match_vars); use Test::More; -use TableChecksum; use VersionParser; +use TableChecksum; use TableParser; use Quoter; use DSNParser; @@ -34,8 +34,7 @@ $sb->create_dbs($dbh, ['test']); my $q = new Quoter(); my $tp = new TableParser(Quoter => $q); -my $vp = new VersionParser(); -my $c = new TableChecksum(Quoter=>$q, VersionParser=>$vp); +my $c = new TableChecksum(Quoter=>$q); my $t; @@ -48,13 +47,6 @@ throws_ok ( 'Algorithm=foo', ); -# Inject the VersionParser with some bogus versions. Later I'll just pass the -# string version number instead of a real DBH, so the version parsing will -# return the value I want. -foreach my $ver( qw(4.0.0 4.1.1) ) { - $vp->{$ver} = $vp->parse($ver); -} - is ( $c->best_algorithm( algorithm => 'CHECKSUM', diff --git a/t/lib/TableSyncChunk.t b/t/lib/TableSyncChunk.t index 6adf3097..79983e98 100644 --- a/t/lib/TableSyncChunk.t +++ b/t/lib/TableSyncChunk.t @@ -46,16 +46,14 @@ diag(`$mysql < $trunk/t/lib/samples/before-TableSyncChunk.sql`); my $q = new Quoter(); my $tp = new TableParser(Quoter => $q); -my $vp = new VersionParser(); my $ms = new MasterSlave(); my $rr = new Retry(); my $chunker = new TableChunker( Quoter => $q, TableParser => $tp ); -my $checksum = new TableChecksum( Quoter => $q, VersionParser => $vp ); +my $checksum = new TableChecksum( Quoter => $q ); my $syncer = new TableSyncer( MasterSlave => $ms, TableChecksum => $checksum, Quoter => $q, - VersionParser => $vp, Retry => $rr, ); diff --git a/t/lib/TableSyncNibble.t b/t/lib/TableSyncNibble.t index a1f9e831..3238e952 100644 --- a/t/lib/TableSyncNibble.t +++ b/t/lib/TableSyncNibble.t @@ -42,7 +42,6 @@ my $mysql = $sb->_use_for('master'); my $q = new Quoter(); my $ms = new MasterSlave(); my $tp = new TableParser(Quoter=>$q); -my $vp = new VersionParser(); my $rr = new Retry(); my $nibbler = new TableNibbler( @@ -51,7 +50,6 @@ my $nibbler = new TableNibbler( ); my $checksum = new TableChecksum( Quoter => $q, - VersionParser => $vp, ); my $chunker = new TableChunker( TableParser => $tp, @@ -62,7 +60,6 @@ my $t = new TableSyncNibble( TableParser => $tp, TableChunker => $chunker, Quoter => $q, - VersionParser => $vp, ); my @rows; @@ -81,7 +78,6 @@ my $syncer = new TableSyncer( MasterSlave => $ms, TableChecksum => $checksum, Quoter => $q, - VersionParser => $vp, Retry => $rr, ); diff --git a/t/lib/TableSyncer.t b/t/lib/TableSyncer.t index 76bbeec7..24ccb83e 100644 --- a/t/lib/TableSyncer.t +++ b/t/lib/TableSyncer.t @@ -50,7 +50,7 @@ elsif ( !$dst_dbh ) { plan skip_all => 'Cannot connect to sandbox slave'; } else { - plan tests => 62; + plan tests => 61; } $sb->create_dbs($dbh, ['test']); @@ -74,28 +74,20 @@ throws_ok( ); throws_ok( sub { new TableSyncer(MasterSlave=>1, Quoter=>1) }, - qr/I need a VersionParser/, - 'VersionParser required' -); -throws_ok( - sub { new TableSyncer(MasterSlave=>1, Quoter=>1, VersionParser=>1) }, qr/I need a TableChecksum/, 'TableChecksum required' ); my $rd = new RowDiff(dbh=>$src_dbh); my $ms = new MasterSlave(); -my $vp = new VersionParser(); my $rt = new Retry(); my $checksum = new TableChecksum( Quoter => $q, - VersionParser => $vp, ); my $syncer = new TableSyncer( MasterSlave => $ms, Quoter => $q, TableChecksum => $checksum, - VersionParser => $vp, DSNParser => $dp, Retry => $rt, ); diff --git a/t/lib/VariableAdvisorRules.t b/t/lib/VariableAdvisorRules.t index 63092478..c21881b7 100644 --- a/t/lib/VariableAdvisorRules.t +++ b/t/lib/VariableAdvisorRules.t @@ -13,6 +13,7 @@ use Test::More tests => 83; use PodParser; use AdvisorRules; +use VersionParser; use VariableAdvisorRules; use Advisor; use PerconaTest; @@ -343,27 +344,27 @@ my @cases = ( advice => [qw(tmp_table_size)], }, { name => "end-of-life mysql version", - mysql_version => '005000087', + mysql_version => VersionParser->new('5.0.87'), advice => ['end-of-life mysql version'], }, { name => "old mysql version 3.22.00", - mysql_version => '00302200', + mysql_version => VersionParser->new('3.22.00'), advice => ['old mysql version', 'end-of-life mysql version'], }, { name => "old mysql version 4.1.1", - mysql_version => '004001001', + mysql_version => VersionParser->new('4.1.1'), advice => ['old mysql version', 'end-of-life mysql version'], }, { name => "old mysql version 5.0.36", - mysql_version => '005000036', + mysql_version => VersionParser->new('5.0.36'), advice => ['old mysql version', 'end-of-life mysql version'], }, { name => "old mysql version 5.1.29", - mysql_version => '005001029', + mysql_version => VersionParser->new('5.1.29'), advice => ['old mysql version'], }, { name => "old mysql version 5.5.0", - mysql_version => '005005000', + mysql_version => VersionParser->new('5.5.0'), advice => [], }, ); diff --git a/t/lib/VersionParser.t b/t/lib/VersionParser.t index 4ecd2531..6b06a2ef 100644 --- a/t/lib/VersionParser.t +++ b/t/lib/VersionParser.t @@ -9,25 +9,83 @@ BEGIN { use strict; use warnings FATAL => 'all'; use English qw(-no_match_vars); -use Test::More tests => 5; +use Test::More tests => 27; use VersionParser; use PerconaTest; -my $vp = new VersionParser; +my $v1 = new_ok "VersionParser", [ "4.1" ], "new from string works"; is( - $vp->parse('5.0.38-Ubuntu_0ubuntu1.1-log'), - '005000038', + "$v1", + "4.1", + "object from string stringifies as expected" +); + +is( + $v1->innodb_version, + 'NO', + 'default ->innodb_version is NO' +); + +my $v2; +$v2 = new_ok "VersionParser", [ qw( major 5 minor 5 revision 5 ) ], "new from parts works"; +is( "$v2", "5.5.5" ); +$v2 = new_ok "VersionParser", [ { qw( major 5 minor 5 revision 5 ) } ], "new from hashref works"; +is( "$v2", "5.5.5" ); + +for my $test ( + [ "5.0.1", "lt", "5.0.2" ], + [ "5.0", "eq", "5.0.2" ], + [ "5.1.0", "gt", "5.0.99" ], + [ "6", "gt", "5.9.9" ], + [ "4", "ne", "5.0.2" ], + [ "5.0.1", "ne", "5.0.2" ], + [ "5.0.1", "eq", "5.0.1" ], + [ "5.0.1", "eq", "5" ], + [ "5.0.1", "lt", "6" ], + [ "5.0.1", "gt", "5.0.09" ], + # TODO: Should these actually happen? + # [ "5.0.1" eq "5.0.10" ] + # [ "5.0.10" lt "5.0.3" ] +) { + my ($v, $cmp, $against, $test) = @$test; + + cmp_ok( VersionParser->new($v), $cmp, $against, "$v $cmp $against" ); +} + +my $c = VersionParser->new("5.5.1"); + +is( + $c->comment("SET NAMES utf8"), + "/*!50501 SET NAMES utf8 */", + "->comment works as expected" +); + +is( + $c->comment('@@hostname,'), + '/*!50501 @@hostname, */', +); + + +is( + VersionParser->new('5.0.38-Ubuntu_0ubuntu1.1-log')->normalized_version, + '50038', 'Parser works on ordinary version', ); is( - $vp->parse('5.5'), - '005005000', + VersionParser->new('5.5')->normalized_version, + '50500', 'Parser works on a simplified version', ); +is( + VersionParser->new('5.0.08')->revision, + '0.8', + 'In 5.0.08, the revsion is 0.8', +); + # Open a connection to MySQL, or skip the rest of the tests. use DSNParser; use Sandbox; @@ -36,13 +94,23 @@ my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp); my $dbh = $sb->get_dbh_for('master'); SKIP: { skip 'Cannot connect to MySQL', 2 unless $dbh; - ok($vp->version_ge($dbh, '3.23.00'), 'Version is > 3.23'); + my $vp = new_ok "VersionParser", [ $dbh ], "new from dbh works"; + cmp_ok($vp, "ge", '3.23.00', 'Version is > 3.23'); unlike( - $vp->innodb_version($dbh), + $vp->innodb_version(), qr/DISABLED/, "InnoDB version" ); + + my ($ver) = $dbh->selectrow_array("SELECT VERSION()"); + $ver =~ s/(\d+\.\d+\.\d+).*/$1/; + + is( + "$vp", + $ver, + "object from dbh stringifies as expected" + ); } # ############################################################################# diff --git a/t/pt-query-digest/execute.t b/t/pt-query-digest/execute.t index 21908630..00247bdd 100644 --- a/t/pt-query-digest/execute.t +++ b/t/pt-query-digest/execute.t @@ -13,7 +13,6 @@ use Test::More; use Sandbox; use PerconaTest; -use VersionParser; # See 101_slowlog_analyses.t for why we shift. shift @INC; # our unshift (above) shift @INC; # PerconaTest's unshift @@ -22,7 +21,6 @@ shift @INC; # Sandbox require "$trunk/bin/pt-query-digest"; my $dp = new DSNParser(opts=>$dsn_opts); -my $vp = new VersionParser(); my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp); my $dbh = $sb->get_dbh_for('master'); diff --git a/t/pt-query-digest/explain.t b/t/pt-query-digest/explain.t index bf7bd6c2..f9aed342 100644 --- a/t/pt-query-digest/explain.t +++ b/t/pt-query-digest/explain.t @@ -13,7 +13,6 @@ use Test::More; use Sandbox; use PerconaTest; -use VersionParser; # See 101_slowlog_analyses.t for why we shift. shift @INC; # our unshift (above) shift @INC; # PerconaTest's unshift @@ -22,7 +21,6 @@ shift @INC; # Sandbox require "$trunk/bin/pt-query-digest"; my $dp = new DSNParser(opts=>$dsn_opts); -my $vp = new VersionParser(); my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp); my $dbh = $sb->get_dbh_for('master'); diff --git a/t/pt-query-digest/explain_partitions.t b/t/pt-query-digest/explain_partitions.t index 820c6659..9a874fb5 100644 --- a/t/pt-query-digest/explain_partitions.t +++ b/t/pt-query-digest/explain_partitions.t @@ -17,14 +17,13 @@ use VersionParser; use Sandbox; my $dp = new DSNParser(opts=>$dsn_opts); -my $vp = new VersionParser(); my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp); my $dbh = $sb->get_dbh_for('master'); if ( !$dbh ) { plan skip_all => 'Cannot connect to sandbox master'; } -elsif ( !$vp->version_ge($dbh, '5.1.0') ) { +elsif ( VersionParser->new($dbh) < '5.1' ) { plan skip_all => 'Sandbox master version not >= 5.1'; } else { diff --git a/t/pt-slave-find/pt-slave-find.t b/t/pt-slave-find/pt-slave-find.t index 7cb1ee17..8488dfed 100644 --- a/t/pt-slave-find/pt-slave-find.t +++ b/t/pt-slave-find/pt-slave-find.t @@ -107,23 +107,25 @@ my $innodb_re = qr/InnoDB version\s+(.*)/; my (@innodb_versions) = $result =~ /$innodb_re/g; $result =~ s/$innodb_re/InnoDB version BUILTIN/g; -my $vp = new VersionParser; +my $master_version = VersionParser->new($master_dbh); +my $slave_version = VersionParser->new($slave_dbh); +my $slave2_version = VersionParser->new($slave_2_dbh); is( $innodb_versions[0], - $vp->innodb_version($master_dbh), + $master_version->innodb_version(), "pt-slave-find gets the right InnoDB version for the master" ); is( $innodb_versions[1], - $vp->innodb_version($slave_dbh), + $slave_version->innodb_version(), "...and for the first slave" ); is( $innodb_versions[2], - $vp->innodb_version($slave_2_dbh), + $slave2_version->innodb_version(), "...and for the first slave" ); diff --git a/t/pt-table-checksum/create_replicate_table.t b/t/pt-table-checksum/create_replicate_table.t index dbd7431a..d7cbc741 100644 --- a/t/pt-table-checksum/create_replicate_table.t +++ b/t/pt-table-checksum/create_replicate_table.t @@ -18,7 +18,6 @@ shift @INC; # PerconaTest's unshift require "$trunk/bin/pt-table-checksum"; my $dp = new DSNParser(opts=>$dsn_opts); -my $vp = new VersionParser(); my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp); my $master_dbh = $sb->get_dbh_for('master'); my $slave_dbh = $sb->get_dbh_for('slave1'); diff --git a/t/pt-table-checksum/filters.t b/t/pt-table-checksum/filters.t index 5bef8bf0..f2d65912 100644 --- a/t/pt-table-checksum/filters.t +++ b/t/pt-table-checksum/filters.t @@ -18,7 +18,6 @@ shift @INC; # our unshift (above) shift @INC; # PerconaTest's unshift require "$trunk/bin/pt-table-checksum"; -my $vp = new VersionParser(); my $dp = new DSNParser(opts=>$dsn_opts); my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp); my $dbh = $sb->get_dbh_for('master'); diff --git a/t/pt-table-sync/bidirectional.t b/t/pt-table-sync/bidirectional.t index 4dde1a13..84760371 100644 --- a/t/pt-table-sync/bidirectional.t +++ b/t/pt-table-sync/bidirectional.t @@ -20,7 +20,6 @@ $Data::Dumper::Indent = 0; $Data::Dumper::Sortkeys = 1; $Data::Dumper::Quotekeys = 0; -my $vp = new VersionParser(); my $dp = new DSNParser(opts=>$dsn_opts); my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp); my $c1_dbh = $sb->get_dbh_for('master'); diff --git a/t/pt-table-sync/binlog_format.t b/t/pt-table-sync/binlog_format.t index 3dc12079..30ca9ff4 100644 --- a/t/pt-table-sync/binlog_format.t +++ b/t/pt-table-sync/binlog_format.t @@ -17,7 +17,6 @@ use Sandbox; require "$trunk/bin/pt-table-sync"; my $output; -my $vp = new VersionParser(); my $dp = new DSNParser(opts=>$dsn_opts); my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp); my $master_dbh = $sb->get_dbh_for('master'); @@ -29,8 +28,8 @@ if ( !$master_dbh ) { elsif ( !$slave_dbh ) { plan skip_all => 'Cannot connect to sandbox slave'; } -elsif ( !$vp->version_ge($master_dbh, '5.1.5') ) { - plan skip_all => 'Requires MySQL 5.1 or newer'; +elsif ( VersionParser->new($master_dbh) < '5.1.5' ) { + plan skip_all => 'Requires MySQL 5.1.5 or newer'; } else { plan tests => 7; diff --git a/t/pt-table-sync/check_privs.t b/t/pt-table-sync/check_privs.t index 26cb4a1f..715ce68c 100644 --- a/t/pt-table-sync/check_privs.t +++ b/t/pt-table-sync/check_privs.t @@ -16,7 +16,6 @@ use Sandbox; require "$trunk/bin/pt-table-sync"; -my $vp = new VersionParser(); my $dp = new DSNParser(opts=>$dsn_opts); my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp); my $master_dbh = $sb->get_dbh_for('master'); diff --git a/t/pt-table-sync/columns.t b/t/pt-table-sync/columns.t index d3b867d5..486d4740 100644 --- a/t/pt-table-sync/columns.t +++ b/t/pt-table-sync/columns.t @@ -16,7 +16,6 @@ use Sandbox; require "$trunk/bin/pt-table-sync"; my $output; -my $vp = new VersionParser(); my $dp = new DSNParser(opts=>$dsn_opts); my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp); my $master_dbh = $sb->get_dbh_for('master'); diff --git a/t/pt-table-sync/filters.t b/t/pt-table-sync/filters.t index 3804f473..34edda36 100644 --- a/t/pt-table-sync/filters.t +++ b/t/pt-table-sync/filters.t @@ -16,7 +16,6 @@ use PerconaTest; use Sandbox; require "$trunk/bin/pt-table-sync"; -my $vp = new VersionParser(); my $dp = new DSNParser(opts=>$dsn_opts); my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp); my $master_dbh = $sb->get_dbh_for('master'); diff --git a/t/pt-table-sync/float_precision.t b/t/pt-table-sync/float_precision.t index 29e96b59..4e01a2c4 100644 --- a/t/pt-table-sync/float_precision.t +++ b/t/pt-table-sync/float_precision.t @@ -16,7 +16,6 @@ use Sandbox; require "$trunk/bin/pt-table-sync"; my $output; -my $vp = new VersionParser(); my $dp = new DSNParser(opts=>$dsn_opts); my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp); my $master_dbh = $sb->get_dbh_for('master'); diff --git a/t/pt-table-sync/force_index.t b/t/pt-table-sync/force_index.t index edb68bcd..44fe11ac 100644 --- a/t/pt-table-sync/force_index.t +++ b/t/pt-table-sync/force_index.t @@ -16,7 +16,6 @@ use Sandbox; require "$trunk/bin/pt-table-sync"; my $output; -my $vp = new VersionParser(); my $dp = new DSNParser(opts=>$dsn_opts); my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp); my $master_dbh = $sb->get_dbh_for('master'); diff --git a/t/pt-table-sync/lock_and_rename.t b/t/pt-table-sync/lock_and_rename.t index 24b47cca..c5332e6e 100644 --- a/t/pt-table-sync/lock_and_rename.t +++ b/t/pt-table-sync/lock_and_rename.t @@ -16,13 +16,12 @@ use Sandbox; require "$trunk/bin/pt-table-sync"; my $output; -my $vp = new VersionParser(); my $dp = new DSNParser(opts=>$dsn_opts); 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') ) { +if ( VersionParser->new($master_dbh) < '5.5' ) { plan skip_all => "This functionality doesn't work correctly on MySQLs earlier than 5.5"; } if ( !$master_dbh ) { diff --git a/t/pt-table-sync/lock_level.t b/t/pt-table-sync/lock_level.t index b4e4d40b..8aa0eae7 100644 --- a/t/pt-table-sync/lock_level.t +++ b/t/pt-table-sync/lock_level.t @@ -16,7 +16,6 @@ use Sandbox; require "$trunk/bin/pt-table-sync"; my $output; -my $vp = new VersionParser(); my $dp = new DSNParser(opts=>$dsn_opts); my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp); my $master_dbh = $sb->get_dbh_for('master'); diff --git a/t/pt-table-sync/specify_column_or_index.t b/t/pt-table-sync/specify_column_or_index.t index 42b4b51e..90a7aa9d 100644 --- a/t/pt-table-sync/specify_column_or_index.t +++ b/t/pt-table-sync/specify_column_or_index.t @@ -16,7 +16,6 @@ use Sandbox; require "$trunk/bin/pt-table-sync"; my $output; -my $vp = new VersionParser(); my $dp = new DSNParser(opts=>$dsn_opts); my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp); my $master_dbh = $sb->get_dbh_for('master'); diff --git a/t/pt-table-sync/sync_to_differnt_db.t b/t/pt-table-sync/sync_to_differnt_db.t index 7cdd1da9..55a9c1bb 100644 --- a/t/pt-table-sync/sync_to_differnt_db.t +++ b/t/pt-table-sync/sync_to_differnt_db.t @@ -16,7 +16,6 @@ use Sandbox; require "$trunk/bin/pt-table-sync"; my $output; -my $vp = new VersionParser(); my $dp = new DSNParser(opts=>$dsn_opts); my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp); my $master_dbh = $sb->get_dbh_for('master'); diff --git a/t/pt-table-sync/triggers.t b/t/pt-table-sync/triggers.t index 33ecfe97..5995c536 100644 --- a/t/pt-table-sync/triggers.t +++ b/t/pt-table-sync/triggers.t @@ -1,4 +1,4 @@ -#!/usr/bin/env perl +// #!/usr/bin/env perl BEGIN { die "The PERCONA_TOOLKIT_BRANCH environment variable is not set.\n" @@ -16,7 +16,6 @@ use Sandbox; require "$trunk/bin/pt-table-sync"; my $output; -my $vp = new VersionParser(); my $dp = new DSNParser(opts=>$dsn_opts); my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp); my $master_dbh = $sb->get_dbh_for('master'); @@ -28,7 +27,7 @@ if ( !$master_dbh ) { elsif ( !$slave_dbh ) { plan skip_all => 'Cannot connect to sandbox slave'; } -elsif ( !$vp->version_ge($master_dbh, '5.0.2') ) { +elsif ( VersionParser->new($master_dbh) < '5.0.2' ) { plan skip_all => 'Sever does not support triggers (< 5.0.2)'; } else { From c00598d3fb3c579100da50e636c1b563c9796fae Mon Sep 17 00:00:00 2001 From: Brian Fraser Date: Wed, 11 Jul 2012 16:46:54 -0300 Subject: [PATCH 08/27] Two VersionParser oversights in pt-table-sync --- bin/pt-table-sync | 3 +-- t/pt-table-sync/triggers.t | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/bin/pt-table-sync b/bin/pt-table-sync index 81c29fa2..cc7e013b 100755 --- a/bin/pt-table-sync +++ b/bin/pt-table-sync @@ -8552,7 +8552,6 @@ sub lock_and_rename { # OptionParser - object # DSNParser - object # Quoter - object -# VersionParser - object # # Returns: # Exit status @@ -8844,7 +8843,7 @@ sub sync_via_replication { sub sync_all { my ( %args ) = @_; my @required_args = qw(dsns plugins OptionParser DSNParser Quoter - VersionParser TableParser MySQLDump); + TableParser MySQLDump); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } diff --git a/t/pt-table-sync/triggers.t b/t/pt-table-sync/triggers.t index 5995c536..55ad5255 100644 --- a/t/pt-table-sync/triggers.t +++ b/t/pt-table-sync/triggers.t @@ -1,4 +1,4 @@ -// #!/usr/bin/env perl +#!/usr/bin/env perl BEGIN { die "The PERCONA_TOOLKIT_BRANCH environment variable is not set.\n" From fdcc9a2290de648b641d69763440a188e0a816fa Mon Sep 17 00:00:00 2001 From: Brian Fraser Date: Thu, 12 Jul 2012 06:25:08 -0300 Subject: [PATCH 09/27] Add missing VersionParser dependency to t/lib/CompareResults.t --- t/lib/CompareResults.t | 1 + 1 file changed, 1 insertion(+) diff --git a/t/lib/CompareResults.t b/t/lib/CompareResults.t index fc760979..333add6a 100644 --- a/t/lib/CompareResults.t +++ b/t/lib/CompareResults.t @@ -11,6 +11,7 @@ use warnings FATAL => 'all'; use English qw(-no_match_vars); use Test::More; +use VersionParser; use Quoter; use TableParser; use DSNParser; From c5555234c1b56a0aa5d4568239c88f063a0a3cff Mon Sep 17 00:00:00 2001 From: Brian Fraser Date: Thu, 12 Jul 2012 23:15:01 -0300 Subject: [PATCH 10/27] VersionParser::normalized_version: Don't use _version_split, use the values from methods instead --- lib/VersionParser.pm | 5 +++-- t/lib/VersionParser.t | 20 +++++++++++++++++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/lib/VersionParser.pm b/lib/VersionParser.pm index a10b04f6..60c23961 100644 --- a/lib/VersionParser.pm +++ b/lib/VersionParser.pm @@ -94,8 +94,9 @@ sub _split_version { # 50120 sub normalized_version { my ( $self ) = @_; - my @version_parts = map { $_ || 0 } $self->_split_version( $self->version ); - my $result = sprintf('%d%02d%02d', @version_parts); + my $result = sprintf('%d%02d%02d', map { $_ || 0 } $self->major, + $self->minor, + $self->revision); PTDEBUG && _d($self->version, 'normalizes to', $result); return $result; } diff --git a/t/lib/VersionParser.t b/t/lib/VersionParser.t index 6b06a2ef..964c2135 100644 --- a/t/lib/VersionParser.t +++ b/t/lib/VersionParser.t @@ -9,7 +9,7 @@ BEGIN { use strict; use warnings FATAL => 'all'; use English qw(-no_match_vars); -use Test::More tests => 27; +use Test::More tests => 29; use VersionParser; use PerconaTest; @@ -80,10 +80,24 @@ is( 'Parser works on a simplified version', ); +my $fractional_version = VersionParser->new('5.0.08'); + is( - VersionParser->new('5.0.08')->revision, + $fractional_version->revision, '0.8', - 'In 5.0.08, the revsion is 0.8', + 'Verson(5.0.08), the revision is 0.8', +); + +is( + "$fractional_version", + "5.0.08", + "Version(5.0.08) stringifies to 5.0.08" +); + +is( + $fractional_version->normalized_version(), + "50000", + "Version(5.0.08) normalizes to 50000" ); # Open a connection to MySQL, or skip the rest of the tests. From ccccafd4df3fda2d80ebe466a72adf45cc02602a Mon Sep 17 00:00:00 2001 From: Brian Fraser Date: Thu, 12 Jul 2012 23:37:53 -0300 Subject: [PATCH 11/27] Test VP->flavor and fix some udnerlaying buys --- lib/VersionParser.pm | 9 ++++++--- t/lib/VersionParser.t | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/lib/VersionParser.pm b/lib/VersionParser.pm index 60c23961..d9e4de0e 100644 --- a/lib/VersionParser.pm +++ b/lib/VersionParser.pm @@ -131,10 +131,13 @@ sub BUILDARGS { if ( @_ == 1 ) { my %args; if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { - my $dbh = $_[0]; - my $query; PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); - if ( eval { $query = $dbh->selectall_hashref(q) } ) { + my $dbh = $_[0]; + my $query = eval { + $dbh->selectall_arrayref(q, { Slice => {} }) + }; + if ( $query ) { + $query = { map { $_->{variable_name} => $_->{value} } @$query }; @args{@methods} = $self->_split_version($query->{version}); $args{flavor} = delete $query->{version_comment} if $query->{version_comment}; diff --git a/t/lib/VersionParser.t b/t/lib/VersionParser.t index 964c2135..70a27b25 100644 --- a/t/lib/VersionParser.t +++ b/t/lib/VersionParser.t @@ -28,6 +28,12 @@ is( 'default ->innodb_version is NO' ); +is( + $v1->flavor(), + "Unknown", + "default ->flavor is Unknown" +); + my $v2; $v2 = new_ok "VersionParser", [ qw( major 5 minor 5 revision 5 ) ], "new from parts works"; is( "$v2", "5.5.5" ); @@ -125,6 +131,16 @@ SKIP: { $ver, "object from dbh stringifies as expected" ); + + my (undef, $flavor) = $dbh->selectrow_array("SHOW VARIABLES LIKE 'version_comment'"); + SKIP: { + skip "Couldn't fetch version_comment from the db", 1 unless $flavor; + is( + $vp->flavor(), + $flavor, + "When created from a dbh, flavor is set through version_comment", + ); + }; } # ############################################################################# From 36d825edb1c121dbd97559c8edb38312f7f1bd8b Mon Sep 17 00:00:00 2001 From: Brian Fraser Date: Fri, 13 Jul 2012 00:42:49 -0300 Subject: [PATCH 12/27] Some changes as per Daniel's review --- lib/VersionParser.pm | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/VersionParser.pm b/lib/VersionParser.pm index d9e4de0e..0262db2f 100644 --- a/lib/VersionParser.pm +++ b/lib/VersionParser.pm @@ -35,6 +35,8 @@ use overload ( fallback => 1, ); +use Carp (); + our $VERSION = 0.01; has major => ( @@ -112,6 +114,8 @@ sub comment { my @methods = qw(major minor revision); sub cmp { my ($left, $right) = @_; + # If the first object is blessed and ->isa( self's class ), then + # just use that; Otherwise, contruct a new VP object from it. my $right_obj = (blessed($right) && $right->isa(ref($left))) ? $right : ref($left)->new($right); @@ -146,9 +150,8 @@ sub BUILDARGS { @args{@methods} = $self->_split_version($query); } else { - PTDEBUG && _d("Couldn't get the version from the dbh: $@"); - # No need to die here; ->new will die on it's own since the version - # is missing + Carp::confess("Couldn't get the version from the dbh while " + . "creating a VersionParser object: $@"); } $args{innodb_version} = eval { $self->_innodb_version($dbh) }; } From 42649bc1d2194c0c0cb1d49645066fc6b6d1be64 Mon Sep 17 00:00:00 2001 From: Brian Fraser Date: Fri, 13 Jul 2012 00:43:17 -0300 Subject: [PATCH 13/27] Add t/lib/Mo and a bunch of tests --- t/lib/Mo/Bar.pm | 4 + t/lib/Mo/Boo.pm | 6 + t/lib/Mo/Foo.pm | 6 + t/lib/Mo/build.t | 51 +++++ t/lib/Mo/buildargs.t | 62 ++++++ t/lib/Mo/coerce.t | 26 +++ t/lib/Mo/extends.t | 24 +++ t/lib/Mo/handles.t | 480 +++++++++++++++++++++++++++++++++++++++++++ t/lib/Mo/init_arg.t | 91 ++++++++ t/lib/Mo/is.t | 26 +++ t/lib/Mo/isa.t | 121 +++++++++++ t/lib/Mo/object.t | 18 ++ t/lib/Mo/required.t | 39 ++++ t/lib/Mo/strict.t | 17 ++ t/lib/Mo/test.t | 140 +++++++++++++ 15 files changed, 1111 insertions(+) create mode 100644 t/lib/Mo/Bar.pm create mode 100644 t/lib/Mo/Boo.pm create mode 100644 t/lib/Mo/Foo.pm create mode 100644 t/lib/Mo/build.t create mode 100644 t/lib/Mo/buildargs.t create mode 100644 t/lib/Mo/coerce.t create mode 100644 t/lib/Mo/extends.t create mode 100644 t/lib/Mo/handles.t create mode 100644 t/lib/Mo/init_arg.t create mode 100644 t/lib/Mo/is.t create mode 100644 t/lib/Mo/isa.t create mode 100644 t/lib/Mo/object.t create mode 100644 t/lib/Mo/required.t create mode 100644 t/lib/Mo/strict.t create mode 100644 t/lib/Mo/test.t diff --git a/t/lib/Mo/Bar.pm b/t/lib/Mo/Bar.pm new file mode 100644 index 00000000..1a4a2410 --- /dev/null +++ b/t/lib/Mo/Bar.pm @@ -0,0 +1,4 @@ +package Bar; +use Mo; +extends 'Foo'; +1; diff --git a/t/lib/Mo/Boo.pm b/t/lib/Mo/Boo.pm new file mode 100644 index 00000000..b6a716ee --- /dev/null +++ b/t/lib/Mo/Boo.pm @@ -0,0 +1,6 @@ +package Boo; +use Mo; + +has 'buff'; + +1; diff --git a/t/lib/Mo/Foo.pm b/t/lib/Mo/Foo.pm new file mode 100644 index 00000000..728da69d --- /dev/null +++ b/t/lib/Mo/Foo.pm @@ -0,0 +1,6 @@ +package Foo; +use Mo; + +has 'stuff'; + +1; diff --git a/t/lib/Mo/build.t b/t/lib/Mo/build.t new file mode 100644 index 00000000..1aa70c0b --- /dev/null +++ b/t/lib/Mo/build.t @@ -0,0 +1,51 @@ +#!/usr/bin/perl + +BEGIN { + die "The PERCONA_TOOLKIT_BRANCH environment variable is not set.\n" + unless $ENV{PERCONA_TOOLKIT_BRANCH} && -d $ENV{PERCONA_TOOLKIT_BRANCH}; + unshift @INC, "$ENV{PERCONA_TOOLKIT_BRANCH}/lib"; +}; + +use strict; +use warnings FATAL => 'all'; +use English qw(-no_match_vars); +use Test::More; + +$main::count = 1; + +package Foo; +use Mo 'build'; +has 'foo' => (is => 'rw'); +sub BUILD { + my $self = shift; + ::is_deeply(\@_, [qw(stuff 1)], "Foo's BUILD doesn't get the class name"); + $self->foo($main::count++); +} + +package Bar; +use Mo; +extends 'Foo'; +has 'bar' => (is => 'rw'); + +package Baz; +use Mo; +extends 'Bar'; +has 'baz' => (is => 'rw'); +sub BUILD { + my $self = shift; + ::is_deeply(\@_, [qw(stuff 1)], "Baz's BUILD doesn't get the class name"); + $self->baz($main::count++); +} + +package Gorch; +use Mo; +extends 'Baz'; +has 'gorch' => (is => 'rw'); + +package main; + +my $g = Gorch->new(stuff => 1); +is $g->foo, 1, 'foo builds first'; +is $g->baz, 2, 'baz builds second'; + +done_testing; diff --git a/t/lib/Mo/buildargs.t b/t/lib/Mo/buildargs.t new file mode 100644 index 00000000..403bd55d --- /dev/null +++ b/t/lib/Mo/buildargs.t @@ -0,0 +1,62 @@ +#!/usr/bin/perl + +BEGIN { + die "The PERCONA_TOOLKIT_BRANCH environment variable is not set.\n" + unless $ENV{PERCONA_TOOLKIT_BRANCH} && -d $ENV{PERCONA_TOOLKIT_BRANCH}; + unshift @INC, "$ENV{PERCONA_TOOLKIT_BRANCH}/lib"; +}; + +use strict; +use warnings FATAL => 'all'; +use English qw(-no_match_vars); +use Test::More; + +$main::count = 0; + +{ + package Nothing; + use Mo; + has nothing_special => ( is => 'rw' ); +} +ok(Nothing->can("BUILDARGS"), "Every class automatically gets buildargs"); + +package Foo; +use Mo; +has 'foo' => (is => 'rw'); +sub BUILDARGS { + my $class = shift; + $main::count++; + $class->SUPER::BUILDARGS(@_); +} + +package Bar; +use Mo; +extends 'Foo'; +has 'bar' => (is => 'rw'); + +package Baz; +use Mo; +extends 'Bar'; +has 'baz' => (is => 'rw'); +sub BUILDARGS { + my $class = shift; + $main::count++; + $class->SUPER::BUILDARGS(@_) +} + +package Gorch; +use Mo; +extends 'Baz'; +has 'gorch' => (is => 'rw'); + +package main; + +$main::count = 0; +my $g = Foo->new; +is $main::count, 1, "A class with no explicit parent inherits SUPER::BUILDARGS from Mo::Object"; + +$main::count = 0; +$g = Gorch->new; +is $main::count, 2, "As does one with a parent that defines it's own BUILDARGS"; + +done_testing; diff --git a/t/lib/Mo/coerce.t b/t/lib/Mo/coerce.t new file mode 100644 index 00000000..2a69fcfa --- /dev/null +++ b/t/lib/Mo/coerce.t @@ -0,0 +1,26 @@ +#!/usr/bin/perl + +BEGIN { + die "The PERCONA_TOOLKIT_BRANCH environment variable is not set.\n" + unless $ENV{PERCONA_TOOLKIT_BRANCH} && -d $ENV{PERCONA_TOOLKIT_BRANCH}; + unshift @INC, "$ENV{PERCONA_TOOLKIT_BRANCH}/lib"; +}; + +use strict; +use warnings FATAL => 'all'; +use English qw(-no_match_vars); +use Test::More; + +plan tests => 2; + +package Foo::coerce; +use Mo; + +has 'stuff' => (coerce => sub { uc $_[0] }); + +package main; + +my $f = Foo::coerce->new(stuff => 'fubar'); +is $f->stuff, 'FUBAR', 'values passed to constructor are successfully coerced'; +$f->stuff('barbaz'); +is $f->stuff, 'BARBAZ', 'values passed to setters are successfully coerced'; diff --git a/t/lib/Mo/extends.t b/t/lib/Mo/extends.t new file mode 100644 index 00000000..4cc738d8 --- /dev/null +++ b/t/lib/Mo/extends.t @@ -0,0 +1,24 @@ +#!/usr/bin/perl + +BEGIN { + die "The PERCONA_TOOLKIT_BRANCH environment variable is not set.\n" + unless $ENV{PERCONA_TOOLKIT_BRANCH} && -d $ENV{PERCONA_TOOLKIT_BRANCH}; + unshift @INC, "$ENV{PERCONA_TOOLKIT_BRANCH}/lib"; +}; + +use strict; +use warnings FATAL => 'all'; +use English qw(-no_match_vars); +use Test::More tests => 4; + +use lib "$ENV{PERCONA_TOOLKIT_BRANCH}/t/lib/Mo"; +use Bar; + +my $b = Bar->new; + +ok $b->isa('Foo'), 'Bar is a subclass of Foo'; + +is "@Bar::ISA", "Foo", 'Extends with multiple classes not supported'; + +ok 'Foo'->can('stuff'), 'Foo is loaded'; +ok not('Bar'->can('buff')), 'Boo is not loaded'; diff --git a/t/lib/Mo/handles.t b/t/lib/Mo/handles.t new file mode 100644 index 00000000..47a283b3 --- /dev/null +++ b/t/lib/Mo/handles.t @@ -0,0 +1,480 @@ +#!/usr/bin/perl + +BEGIN { + die "The PERCONA_TOOLKIT_BRANCH environment variable is not set.\n" + unless $ENV{PERCONA_TOOLKIT_BRANCH} && -d $ENV{PERCONA_TOOLKIT_BRANCH}; + unshift @INC, "$ENV{PERCONA_TOOLKIT_BRANCH}/lib"; +}; + +use strict; +use warnings FATAL => 'all'; +use English qw(-no_match_vars); +use Test::More tests => 82; + + +# ------------------------------------------------------------------- +# HASH handles +# ------------------------------------------------------------------- +# the canonical form of of the 'handles' +# option is the hash ref mapping a +# method name to the delegated method name + +{ + package Foo; + use Mo qw(is required handles default builder); + + has 'bar' => (is => 'rw', default => sub { 10 }); + + sub baz { 42 } + + package Bar; + use Mo qw(is required handles default builder); + + has 'foo' => ( + is => 'rw', + default => sub { Foo->new }, + handles => { + 'foo_bar' => 'bar', + foo_baz => 'baz', + 'foo_bar_to_20' => [ bar => 20 ], + }, + ); +} + +my $bar = Bar->new; +isa_ok($bar, 'Bar'); + +ok($bar->foo, '... we have something in bar->foo'); +isa_ok($bar->foo, 'Foo'); + +is($bar->foo->bar, 10, '... bar->foo->bar returned the right default'); + +can_ok($bar, 'foo_bar'); +is($bar->foo_bar, 10, '... bar->foo_bar delegated correctly'); + +# change the value ... + +$bar->foo->bar(30); + +# and make sure the delegation picks it up + +is($bar->foo->bar, 30, '... bar->foo->bar returned the right (changed) value'); +is($bar->foo_bar, 30, '... bar->foo_bar delegated correctly'); + +# change the value through the delegation ... + +$bar->foo_bar(50); + +# and make sure everyone sees it + +is($bar->foo->bar, 50, '... bar->foo->bar returned the right (changed) value'); +is($bar->foo_bar, 50, '... bar->foo_bar delegated correctly'); + +# change the object we are delegating too + +my $foo = Foo->new(bar => 25); +isa_ok($foo, 'Foo'); + +is($foo->bar, 25, '... got the right foo->bar'); + +local $@; +eval { $bar->foo($foo) }; +is $@, '', '... assigned the new Foo to Bar->foo'; + +is($bar->foo, $foo, '... assigned bar->foo with the new Foo'); + +is($bar->foo->bar, 25, '... bar->foo->bar returned the right result'); +is($bar->foo_bar, 25, '... and bar->foo_bar delegated correctly again'); + +# curried handles +$bar->foo_bar_to_20; +is($bar->foo_bar, 20, '... correctly curried a single argument'); + +# ------------------------------------------------------------------- +# ARRAY handles +# ------------------------------------------------------------------- +# we also support an array based format +# which assumes that the name is the same +# on either end + +{ + package Engine; + use Mo qw(is required handles default builder); + + sub go { 'Engine::go' } + sub stop { 'Engine::stop' } + + package Car; + use Mo qw(is required handles default builder); + + has 'engine' => ( + is => 'rw', + default => sub { Engine->new }, + handles => [ 'go', 'stop' ] + ); +} + +my $car = Car->new; +isa_ok($car, 'Car'); + +isa_ok($car->engine, 'Engine'); +can_ok($car->engine, 'go'); +can_ok($car->engine, 'stop'); + +is($car->engine->go, 'Engine::go', '... got the right value from ->engine->go'); +is($car->engine->stop, 'Engine::stop', '... got the right value from ->engine->stop'); + +can_ok($car, 'go'); +can_ok($car, 'stop'); + +is($car->go, 'Engine::go', '... got the right value from ->go'); +is($car->stop, 'Engine::stop', '... got the right value from ->stop'); + +# ------------------------------------------------------------------- +# REGEXP handles +# ------------------------------------------------------------------- +# and we support regexp delegation + +{ + package Baz; + use Mo qw(is required handles default builder); + + sub foo { 'Baz::foo' } + sub bar { 'Baz::bar' } + sub boo { 'Baz::boo' } + + package Baz::Proxy1; + use Mo qw(is required handles default builder); + + has 'baz' => ( + is => 'ro', + isa => 'Baz', + default => sub { Baz->new }, + handles => qr/.*/ + ); + + package Baz::Proxy2; + use Mo qw(is required handles default builder); + + has 'baz' => ( + is => 'ro', + isa => 'Baz', + default => sub { Baz->new }, + handles => qr/.oo/ + ); + + package Baz::Proxy3; + use Mo qw(is required handles default builder); + + has 'baz' => ( + is => 'ro', + isa => 'Baz', + default => sub { Baz->new }, + handles => qr/b.*/ + ); +} + +{ + my $baz_proxy = Baz::Proxy1->new; + isa_ok($baz_proxy, 'Baz::Proxy1'); + + can_ok($baz_proxy, 'baz'); + isa_ok($baz_proxy->baz, 'Baz'); + + can_ok($baz_proxy, 'foo'); + can_ok($baz_proxy, 'bar'); + can_ok($baz_proxy, 'boo'); + + is($baz_proxy->foo, 'Baz::foo', '... got the right proxied return value'); + is($baz_proxy->bar, 'Baz::bar', '... got the right proxied return value'); + is($baz_proxy->boo, 'Baz::boo', '... got the right proxied return value'); +} +{ + my $baz_proxy = Baz::Proxy2->new; + isa_ok($baz_proxy, 'Baz::Proxy2'); + + can_ok($baz_proxy, 'baz'); + isa_ok($baz_proxy->baz, 'Baz'); + + can_ok($baz_proxy, 'foo'); + can_ok($baz_proxy, 'boo'); + + is($baz_proxy->foo, 'Baz::foo', '... got the right proxied return value'); + is($baz_proxy->boo, 'Baz::boo', '... got the right proxied return value'); +} +{ + my $baz_proxy = Baz::Proxy3->new; + isa_ok($baz_proxy, 'Baz::Proxy3'); + + can_ok($baz_proxy, 'baz'); + isa_ok($baz_proxy->baz, 'Baz'); + + can_ok($baz_proxy, 'bar'); + can_ok($baz_proxy, 'boo'); + + is($baz_proxy->bar, 'Baz::bar', '... got the right proxied return value'); + is($baz_proxy->boo, 'Baz::boo', '... got the right proxied return value'); +} + +# ------------------------------------------------------------------- +# ROLE handles +# ------------------------------------------------------------------- +=begin +{ + package Foo::Bar; + use Moose::Role; + + requires 'foo'; + requires 'bar'; + + package Foo::Baz; + use Mo qw(is required handles default builder); + + sub foo { 'Foo::Baz::FOO' } + sub bar { 'Foo::Baz::BAR' } + sub baz { 'Foo::Baz::BAZ' } + + package Foo::Thing; + use Mo qw(is required handles default builder); + + has 'thing' => ( + is => 'rw', + isa => 'Foo::Baz', + handles => 'Foo::Bar', + ); + + package Foo::OtherThing; + use Mo qw(is required handles default builder); + use Moose::Util::TypeConstraints; + + has 'other_thing' => ( + is => 'rw', + isa => 'Foo::Baz', + handles => Mooose::Util::TypeConstraints::find_type_constraint('Foo::Bar'), + ); +} + +{ + my $foo = Foo::Thing->new(thing => Foo::Baz->new); + isa_ok($foo, 'Foo::Thing'); + isa_ok($foo->thing, 'Foo::Baz'); + + ok($foo->meta->has_method('foo'), '... we have the method we expect'); + ok($foo->meta->has_method('bar'), '... we have the method we expect'); + ok(!$foo->meta->has_method('baz'), '... we dont have the method we expect'); + + is($foo->foo, 'Foo::Baz::FOO', '... got the right value'); + is($foo->bar, 'Foo::Baz::BAR', '... got the right value'); + is($foo->thing->baz, 'Foo::Baz::BAZ', '... got the right value'); +} + +{ + my $foo = Foo::OtherThing->new(other_thing => Foo::Baz->new); + isa_ok($foo, 'Foo::OtherThing'); + isa_ok($foo->other_thing, 'Foo::Baz'); + + ok($foo->meta->has_method('foo'), '... we have the method we expect'); + ok($foo->meta->has_method('bar'), '... we have the method we expect'); + ok(!$foo->meta->has_method('baz'), '... we dont have the method we expect'); + + is($foo->foo, 'Foo::Baz::FOO', '... got the right value'); + is($foo->bar, 'Foo::Baz::BAR', '... got the right value'); + is($foo->other_thing->baz, 'Foo::Baz::BAZ', '... got the right value'); +} +=cut +# ------------------------------------------------------------------- +# AUTOLOAD & handles +# ------------------------------------------------------------------- + +{ + package Foo::Autoloaded; + use Mo qw(is required handles default builder); + + sub AUTOLOAD { + my $self = shift; + + my $name = our $AUTOLOAD; + $name =~ s/.*://; # strip fully-qualified portion + + if (@_) { + return $self->{$name} = shift; + } else { + return $self->{$name}; + } + } + + package Bar::Autoloaded; + use Mo qw(is required handles default builder); + + has 'foo' => ( + is => 'rw', + default => sub { Foo::Autoloaded->new }, + handles => { 'foo_bar' => 'bar' } + ); + + package Baz::Autoloaded; + use Mo qw(is required handles default builder); + + has 'foo' => ( + is => 'rw', + default => sub { Foo::Autoloaded->new }, + handles => ['bar'] + ); + + package Goorch::Autoloaded; + use Mo qw(is required handles default builder); + + eval { + has 'foo' => ( + is => 'rw', + default => sub { Foo::Autoloaded->new }, + handles => qr/bar/ + ); + }; + ::isnt($@, '', '... you cannot delegate to AUTOLOADED class with regexp' ); +} + +# check HASH based delegation w/ AUTOLOAD + +{ + my $bar = Bar::Autoloaded->new; + isa_ok($bar, 'Bar::Autoloaded'); + + ok($bar->foo, '... we have something in bar->foo'); + isa_ok($bar->foo, 'Foo::Autoloaded'); + + # change the value ... + + $bar->foo->bar(30); + + # and make sure the delegation picks it up + + is($bar->foo->bar, 30, '... bar->foo->bar returned the right (changed) value'); + is($bar->foo_bar, 30, '... bar->foo_bar delegated correctly'); + + # change the value through the delegation ... + + $bar->foo_bar(50); + + # and make sure everyone sees it + + is($bar->foo->bar, 50, '... bar->foo->bar returned the right (changed) value'); + is($bar->foo_bar, 50, '... bar->foo_bar delegated correctly'); + + # change the object we are delegating too + + my $foo = Foo::Autoloaded->new; + isa_ok($foo, 'Foo::Autoloaded'); + + $foo->bar(25); + + is($foo->bar, 25, '... got the right foo->bar'); + + local $@; + eval { $bar->foo($foo) }; + is($@, '', '... assigned the new Foo to Bar->foo' ); + + is($bar->foo, $foo, '... assigned bar->foo with the new Foo'); + + is($bar->foo->bar, 25, '... bar->foo->bar returned the right result'); + is($bar->foo_bar, 25, '... and bar->foo_bar delegated correctly again'); +} + +# check ARRAY based delegation w/ AUTOLOAD + +{ + my $baz = Baz::Autoloaded->new; + isa_ok($baz, 'Baz::Autoloaded'); + + ok($baz->foo, '... we have something in baz->foo'); + isa_ok($baz->foo, 'Foo::Autoloaded'); + + # change the value ... + + $baz->foo->bar(30); + + # and make sure the delegation picks it up + + is($baz->foo->bar, 30, '... baz->foo->bar returned the right (changed) value'); + is($baz->bar, 30, '... baz->foo_bar delegated correctly'); + + # change the value through the delegation ... + + $baz->bar(50); + + # and make sure everyone sees it + + is($baz->foo->bar, 50, '... baz->foo->bar returned the right (changed) value'); + is($baz->bar, 50, '... baz->foo_bar delegated correctly'); + + # change the object we are delegating too + + my $foo = Foo::Autoloaded->new; + isa_ok($foo, 'Foo::Autoloaded'); + + $foo->bar(25); + + is($foo->bar, 25, '... got the right foo->bar'); + + is( exception { + $baz->foo($foo); + }, undef, '... assigned the new Foo to Baz->foo' ); + + is($baz->foo, $foo, '... assigned baz->foo with the new Foo'); + + is($baz->foo->bar, 25, '... baz->foo->bar returned the right result'); + is($baz->bar, 25, '... and baz->foo_bar delegated correctly again'); +} + +# Make sure that a useful error message is thrown when the delegation target is +# not an object +{ + my $i = Bar->new(foo => undef); + local $@; + eval { $i->foo_bar }; + like($@, qr/is not defined/, 'useful error from unblessed reference' ); + + my $j = Bar->new(foo => []); + local $@; + eval { $j->foo_bar }; + like($@, qr/is not an object \(got 'ARRAY/, 'useful error from unblessed reference' ); + + my $k = Bar->new(foo => "Foo"); + local $@; + eval { $k->foo_baz }; + is( $@, '', "but not for class name" ); +} + +{ + package Delegator; + use Mo qw(is required handles default builder); + + sub full { 1 } + sub stub; + + local $@; + eval { + has d1 => ( + isa => 'X', + handles => ['full'], + ); + }; + ::like( + $@, + qr/\QYou cannot overwrite a locally defined method (full) with a delegation/, + 'got an error when trying to declare a delegation method that overwrites a local method' + ); + + local $@; + eval { has d2 => ( + isa => 'X', + handles => ['stub'], + ); + }; + ::is( + $@, + '', + 'no error when trying to declare a delegation method that overwrites a stub method' + ); +} + diff --git a/t/lib/Mo/init_arg.t b/t/lib/Mo/init_arg.t new file mode 100644 index 00000000..b6fab8d4 --- /dev/null +++ b/t/lib/Mo/init_arg.t @@ -0,0 +1,91 @@ +#!/usr/bin/perl + +BEGIN { + die "The PERCONA_TOOLKIT_BRANCH environment variable is not set.\n" + unless $ENV{PERCONA_TOOLKIT_BRANCH} && -d $ENV{PERCONA_TOOLKIT_BRANCH}; + unshift @INC, "$ENV{PERCONA_TOOLKIT_BRANCH}/lib"; +}; + +use strict; +use warnings FATAL => 'all'; +use English qw(-no_match_vars); +use Test::More; + + + +{ + package Foo; + use Mo qw( is init_arg ); + + eval { + has 'foo' => ( + is => "rw", + init_arg => undef, + ); + }; + ::ok(!$@, '... created the attr okay'); +} + +{ + my $foo = Foo->new( foo => "bar" ); + isa_ok($foo, 'Foo'); + + is( $foo->foo, undef, "field is not set via init arg" ); + + $foo->foo("blah"); + + is( $foo->foo, "blah", "field is set via setter" ); +} + +{ + package Foo; + + eval { + has 'foo2' => ( + is => "rw", + init_arg => undef, + ); + }; + ::ok(!$@, '... adding a second attribute with init_arg works'); +} + +{ + my $foo = Foo->new( foo => "bar", foo2 => "baz" ); + + is( $foo->foo, undef, "foo is not set via init arg" ); + is( $foo->foo2, undef, "foo2 is not set via init arg" ); + + $foo->foo("blah"); + $foo->foo2("bluh"); + + is( $foo->foo, "blah", "foo is set via setter" ); + is( $foo->foo2, "bluh", "foo2 is set via setter" ); +} + +{ + package Foo2; + use Mo qw( is init_arg clearer default ); + + my $counter; + eval { + has 'auto_foo' => ( + is => "ro", + init_arg => undef, + default => sub { $counter++ ? "Foo" : "Bar" }, + clearer => 'clear_auto_foo', + ); + }; + ::ok(!$@, '... attribute with init_arg+default+clearer+is works'); +} + +{ + my $foo = Foo2->new( auto_foo => 1234 ); + + is( $foo->auto_foo, "Bar", "auto_foo is not set via init arg, but by the default" ); + + $foo->clear_auto_foo(); + + is( $foo->auto_foo, "Foo", "auto_foo calls default again if cleared" ); +} + +done_testing; diff --git a/t/lib/Mo/is.t b/t/lib/Mo/is.t new file mode 100644 index 00000000..154f86a4 --- /dev/null +++ b/t/lib/Mo/is.t @@ -0,0 +1,26 @@ +#!/usr/bin/perl + +BEGIN { + die "The PERCONA_TOOLKIT_BRANCH environment variable is not set.\n" + unless $ENV{PERCONA_TOOLKIT_BRANCH} && -d $ENV{PERCONA_TOOLKIT_BRANCH}; + unshift @INC, "$ENV{PERCONA_TOOLKIT_BRANCH}/lib"; +}; + +use strict; +use warnings FATAL => 'all'; +use English qw(-no_match_vars); +use Test::More; + +plan tests => 2; + +package Foo::is; +use Mo qw(is); + +has 'stuff' => (is => 'ro'); + +package main; + +my $f = Foo::is->new(stuff => 'foo'); +is $f->stuff, 'foo', 'values passed to constructor are successfully accepted'; +eval { $f->stuff('barbaz') }; +ok $@, 'setting values after initialization throws an exception'; diff --git a/t/lib/Mo/isa.t b/t/lib/Mo/isa.t new file mode 100644 index 00000000..e90519dc --- /dev/null +++ b/t/lib/Mo/isa.t @@ -0,0 +1,121 @@ +#!/usr/bin/perl + +BEGIN { + die "The PERCONA_TOOLKIT_BRANCH environment variable is not set.\n" + unless $ENV{PERCONA_TOOLKIT_BRANCH} && -d $ENV{PERCONA_TOOLKIT_BRANCH}; + unshift @INC, "$ENV{PERCONA_TOOLKIT_BRANCH}/lib"; +}; + +use strict; +use warnings FATAL => 'all'; +use English qw(-no_match_vars); +use Test::More tests => 80; + +sub dies_ok (&;$) { + my $code = shift; + my $name = shift; + + ok( !eval{ $code->() }, $name ) + or diag( "expected an exception but none was raised" ); +} + +sub lives_ok (&;$) { + my $code = shift; + my $name = shift; + + eval{ $code->() }; + is($@, '', $name ); +} + +package Foo::isa; +use Mo qw(isa); + +my @types = qw(Bool Num Int Str ArrayRef CodeRef HashRef RegexpRef); +my @refs = ([], sub { }, {}, qr( )); +has( "my$_" => ( isa => $_ ) ) for @types; +has( myFoo => ( isa => "Foo::isa" ) ); + +package main; + +my $foo = Foo::isa->new( myStr => "abcdefg" ); + +# Bool: +lives_ok { ok !$foo->myBool(undef) } "Bool attr set to undef"; +lives_ok { is $foo->myBool(1), 1 } "Bool attr set to 1"; +is $foo->myBool, 1, "new value of \$foo->myBool as expected"; +lives_ok { is $foo->myBool(1e0), 1 } "Bool attr set to 1e0"; +dies_ok { $foo->myBool("1f0") } "Bool attr set to 1f0 dies"; +lives_ok { is $foo->myBool(""), "" } "Bool attr set to empty string"; +is $foo->myBool, "", "new value of \$foo->myBool as expected"; +lives_ok { is $foo->myBool(0), 0 } "Bool attr set to 0"; +lives_ok { is $foo->myBool(0.0), 0 } "Bool attr set to 0.0"; +lives_ok { is $foo->myBool(0e0), 0 } "Bool attr set to 0e0"; +dies_ok { $foo->myBool("0.0") } "Bool attr set to stringy 0.0 dies"; + +# Bool tests from Mouse: +open(my $FH, "<", $0) or die "Could not open $0 for the test"; +my $msg = q(Bool rejects anything which is not a 1 or 0 or "" or undef"); +lives_ok { $foo->myBool(0) } $msg; +lives_ok { $foo->myBool(1) } $msg; +dies_ok { $foo->myBool(100) } $msg; +lives_ok { $foo->myBool("") } $msg; +dies_ok { $foo->myBool("Foo") } $msg; +dies_ok { $foo->myBool([]) } $msg; +dies_ok { $foo->myBool({}) } $msg; +dies_ok { $foo->myBool(sub {}) } $msg; +dies_ok { $foo->myBool(\"") } $msg; +dies_ok { $foo->myBool(*STDIN) } $msg; +dies_ok { $foo->myBool(\*STDIN) } $msg; +dies_ok { $foo->myBool($FH) } $msg; +dies_ok { $foo->myBool(qr/../) } $msg; +dies_ok { $foo->myBool(bless {}, "Foo") } $msg; +lives_ok { $foo->myBool(undef) } $msg; + +# Num: +lives_ok { is $foo->myNum(5.5), 5.5 } "Num attr set to decimal"; +is $foo->myNum, 5.5, "new value of \$foo->myNum as expected"; +lives_ok { is $foo->myNum(5), 5 } "Num attr set to integer"; +lives_ok { is $foo->myNum(5e0), 5 } "Num attr set to 5e0"; +dies_ok { $foo->myBool("5f0") } "Bool attr set to 5f0 dies"; +lives_ok { is $foo->myNum("5.5"), 5.5 } "Num attr set to stringy decimal"; + +# Int: +lives_ok { is $foo->myInt(0), 0 } "Int attr set to 0"; +lives_ok { is $foo->myInt(1), 1 } "Int attr set to 1"; +lives_ok { is $foo->myInt(1e0), 1 } "Int attr set to 1e0"; +is $foo->myInt, 1, "new value of \$foo->myInt as expected"; +dies_ok { $foo->myInt("") } "Int attr set to empty string dies"; +dies_ok { $foo->myInt(5.5) } "Int attr set to decimal dies"; + +# Str: +is $foo->myStr, "abcdefg", "Str passed to constructor accepted"; +lives_ok { is $foo->myStr("hijklmn"), "hijklmn" } "Str attr set to a string"; +is $foo->myStr, "hijklmn", "new value of \$foo->myStr as expected"; +lives_ok { is $foo->myStr(5.5), 5.5 } "Str attr set to a decimal value"; + +# Class instance: +lives_ok { is $foo->myFoo($foo), $foo } "Class instance attr set to self"; +isa_ok $foo->myFoo, "Foo::isa", "new value of \$foo->myFoo as expected"; +dies_ok { $foo->myFoo({}) } "Class instance attr set to hash dies"; + +# Class name: +my $class = ref($foo); +lives_ok { is $foo->myFoo($class), $class } "Class instance attr set to classname"; +is $foo->myFoo, $class, "new value of \$foo->myFoo as expected"; + +# Refs: +for my $i (4..7) { + my $method = "my" . $types[$i]; + lives_ok( + sub { $foo->$method($refs[$i - 4]) }, + "$types[$i] attr set to correct reference type" ); } +for my $i (4..7) { + my $method = "my" . $types[$i]; + dies_ok( + sub { $foo->$method($refs[(3 + $i) % 4]) }, + "$types[$i] attr set to incorrect reference type dies" ); } + +# All but Bool vs undef: +for my $type (@types[1..$#types]) { + my $method = "my$type"; + dies_ok { $foo->$method(undef) } "$type attr set to undef dies" } diff --git a/t/lib/Mo/object.t b/t/lib/Mo/object.t new file mode 100644 index 00000000..9aee9a21 --- /dev/null +++ b/t/lib/Mo/object.t @@ -0,0 +1,18 @@ +#!/usr/bin/perl + +BEGIN { + die "The PERCONA_TOOLKIT_BRANCH environment variable is not set.\n" + unless $ENV{PERCONA_TOOLKIT_BRANCH} && -d $ENV{PERCONA_TOOLKIT_BRANCH}; + unshift @INC, "$ENV{PERCONA_TOOLKIT_BRANCH}/lib"; +}; + +use strict; +use warnings FATAL => 'all'; +use English qw(-no_match_vars); +use Test::More tests => 2; +use lib "$ENV{PERCONA_TOOLKIT_BRANCH}/t/lib/Mo"; + +{ package Clean; use Foo; } + +is_deeply([ @Clean::ISA ], [], "Didn't mess with caller's ISA"); +is(Clean->can('has'), undef, "Didn't export anything"); diff --git a/t/lib/Mo/required.t b/t/lib/Mo/required.t new file mode 100644 index 00000000..569f46cb --- /dev/null +++ b/t/lib/Mo/required.t @@ -0,0 +1,39 @@ +#!/usr/bin/perl + +BEGIN { + die "The PERCONA_TOOLKIT_BRANCH environment variable is not set.\n" + unless $ENV{PERCONA_TOOLKIT_BRANCH} && -d $ENV{PERCONA_TOOLKIT_BRANCH}; + unshift @INC, "$ENV{PERCONA_TOOLKIT_BRANCH}/lib"; +}; + +use strict; +use warnings FATAL => 'all'; +use English qw(-no_match_vars); +use Test::More; + +plan tests => 3; + +#============ +package Foo::required; +use Mo qw(required); + +has 'stuff' => (required => 1); +has 'stuff2' => (required => 1); +has 'foo' => (); +#============ +package Foo::required_is; +use Mo qw(required); + +has 'stuff' => (required => 1, is => 'ro'); +#============ + +package main; + +my $f0 = eval { Foo::required->new(stuff2 => 'foobar') }; +like $@, qr/^\QAttribute (stuff) is required/, 'Mo dies when a required value is not provided'; + +my $f = Foo::required->new(stuff => 'fubar', stuff2 => 'foobar'); +is $f->stuff, 'fubar', 'Object is correctly initialized when required values are provided'; + +my $f2 = Foo::required_is->new(stuff => 'fubar'); +is $f2->stuff, 'fubar', 'Object is correctly initialized when required is combined with is'; diff --git a/t/lib/Mo/strict.t b/t/lib/Mo/strict.t new file mode 100644 index 00000000..8669ca43 --- /dev/null +++ b/t/lib/Mo/strict.t @@ -0,0 +1,17 @@ +#!/usr/bin/perl + +BEGIN { + die "The PERCONA_TOOLKIT_BRANCH environment variable is not set.\n" + unless $ENV{PERCONA_TOOLKIT_BRANCH} && -d $ENV{PERCONA_TOOLKIT_BRANCH}; + unshift @INC, "$ENV{PERCONA_TOOLKIT_BRANCH}/lib"; +}; + +use strict; +use warnings FATAL => 'all'; +use English qw(-no_match_vars); +use Test::More tests => 1; + +eval 'package Foo; use Mo; $x = 1'; + +like $@, qr/Global symbol "\$x" requires explicit package name/, + 'Mo is strict'; diff --git a/t/lib/Mo/test.t b/t/lib/Mo/test.t new file mode 100644 index 00000000..80239654 --- /dev/null +++ b/t/lib/Mo/test.t @@ -0,0 +1,140 @@ +#!/usr/bin/perl + +BEGIN { + die "The PERCONA_TOOLKIT_BRANCH environment variable is not set.\n" + unless $ENV{PERCONA_TOOLKIT_BRANCH} && -d $ENV{PERCONA_TOOLKIT_BRANCH}; + unshift @INC, "$ENV{PERCONA_TOOLKIT_BRANCH}/lib"; +}; + +use strict; +use warnings FATAL => 'all'; +use English qw(-no_match_vars); +use Test::More; + +plan tests => 39; + +#============ +package Foo; +use Mo; + +has 'this'; + +#============ +package main; + +ok defined(&Foo::has), 'Mo exports has'; +ok defined(&Foo::extends), 'Mo exports extends'; +ok not(defined(&Foo::new)), 'Mo does not export new'; +ok 'Foo'->isa('Mo::Object'), 'Foo isa Mo::Object'; +is "@Foo::ISA", "Mo::Object", '@Foo::ISA is Mo::Object'; +ok 'Foo'->can('new'), 'Foo can new'; +ok 'Foo'->can('this'), 'Foo can this'; + +my $f = 'Foo'->new; + +ok not(exists($f->{this})), 'this does not exist'; +ok not(defined($f->this)), 'this is not defined'; + +$f->this("it"); + +is $f->this, 'it', 'this is it'; +is $f->{this}, 'it', '{this} is it'; + +$f->this("that"); + +is $f->this, 'that', 'this is that'; +is $f->{this}, 'that', '{this} is that'; + +$f->this(undef); + +ok not(defined($f->this)), 'this is not defined'; +ok not(defined($f->{this})), '{this} is not defined'; + +#============ +package Bar; +use Mo 'builder', 'default'; +extends 'Foo'; + +has 'that'; +has them => default => sub {[]}; +has plop => ( + is => 'xy', + default => sub { my $self = shift; "plop: " . $self->that }, +); +has 'plip'; +has bridge => builder => 'bridge_builder'; +use constant bridge_builder => 'A Bridge'; +has guess => ( + default => sub {'me me me'}, + builder => 'bridge_builder', +); + +#============ +package main; + +ok 'Bar'->isa('Mo::Object'), 'Bar isa Mo::Object'; +ok 'Bar'->isa('Foo'), 'Bar isa Foo'; +is "@Bar::ISA", 'Foo', '@Bar::ISA is Foo'; +ok 'Bar'->can('new'), 'Bar can new'; +ok 'Bar'->can('this'), 'Bar can this'; +ok 'Bar'->can('that'), 'Bar can that'; +ok 'Bar'->can('them'), 'Bar can them'; + +my $b = Bar->new( + this => 'thing', + that => 'thong', +); + +is ref($b), 'Bar', 'Object created'; +ok $b->isa('Foo'), 'Inheritance works'; +ok $b->isa('Mo::Object'), 'Bar isa Mo::Object since Foo isa Mo::Object'; +is $b->this, 'thing', 'Read works in parent class'; +is $b->that, 'thong', 'Read works in current class'; +is ref($b->them), 'ARRAY', 'default works'; +is $b->plop, 'plop: thong', 'default works as a method call'; +$b->that("thung"); +$b->plop(undef); +ok not(defined $b->plop), 'plop is undef'; +delete $b->{plop}; +is $b->plop, 'plop: thung', 'default works again'; +$b->that("thyng"); +is $b->plop, 'plop: thung', 'default works again'; +is $b->plip, undef, 'no default is undef'; +is $b->bridge, 'A Bridge', 'builder works'; +is $b->guess, 'me me me', 'default trumps builder'; + +#============ +package Baz; +use Mo 'build'; + +has 'foo'; + +sub BUILD { + my $self = shift; + $self->foo(5); +} + +#============ +package Maz; +use Mo; +extends 'Baz'; + +has 'bar'; + +sub BUILD { + my $self = shift; + $self->SUPER::BUILD(); + $self->bar(7); +} + +#============ +package main; + +my $baz = Baz->new; +is $baz->foo, 5, 'BUILD works'; + +$_ = 5; +my $maz = Maz->new; +is $_, 5, '$_ is untouched'; +is $maz->foo, 5, 'BUILD works again'; +is $maz->bar, 7, 'BUILD works in parent class'; From 1d008666693ea4cb28f53053eec925c67e52ed51 Mon Sep 17 00:00:00 2001 From: Brian Fraser Date: Fri, 13 Jul 2012 01:16:04 -0300 Subject: [PATCH 14/27] MasterSlave: Drop dependency on VersionParser --- lib/MasterSlave.pm | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/lib/MasterSlave.pm b/lib/MasterSlave.pm index ef74a01a..d343e52c 100644 --- a/lib/MasterSlave.pm +++ b/lib/MasterSlave.pm @@ -273,16 +273,9 @@ sub _find_slaves_by_hosts { sub get_connected_slaves { my ( $self, $dbh ) = @_; - # Check for the PROCESS privilege. SHOW GRANTS operates differently - # before 4.1.2: it requires "FROM ..." and it's not until 4.0.6 that - # CURRENT_USER() is available. So for versions <4.1.2 we get current - # user with USER(), quote it, and then add it to statement. + # Check for the PROCESS privilege. my $show = "SHOW GRANTS FOR "; my $user = 'CURRENT_USER()'; - if ( VersionParser->new($dbh) < '4.1.2' ) { - $user = $dbh->selectrow_arrayref('SELECT USER()')->[0]; - $user =~ s/([^@]+)@(.+)/'$1'\@'$2'/; - } my $sql = $show . $user; PTDEBUG && _d($dbh, $sql); From 541d001d2a7208e30fcb4ee594d05c9e6aacbd06 Mon Sep 17 00:00:00 2001 From: Brian Fraser Date: Fri, 13 Jul 2012 01:25:31 -0300 Subject: [PATCH 15/27] Updated modules --- bin/pt-archiver | 23 +- bin/pt-deadlock-logger | 19 +- bin/pt-duplicate-key-checker | 19 +- bin/pt-find | 19 +- bin/pt-heartbeat | 640 ----------------------------------- bin/pt-index-usage | 19 +- bin/pt-kill | 4 - bin/pt-online-schema-change | 23 +- bin/pt-query-advisor | 2 +- bin/pt-query-digest | 7 +- bin/pt-slave-find | 23 +- bin/pt-slave-restart | 23 +- bin/pt-table-checksum | 23 +- bin/pt-table-sync | 23 +- bin/pt-table-usage | 2 +- bin/pt-upgrade | 19 +- bin/pt-variable-advisor | 19 +- 17 files changed, 159 insertions(+), 748 deletions(-) diff --git a/bin/pt-archiver b/bin/pt-archiver index 548bcd39..3bade8ee 100755 --- a/bin/pt-archiver +++ b/bin/pt-archiver @@ -2314,6 +2314,8 @@ use overload ( fallback => 1, ); +use Carp (); + our $VERSION = 0.01; has major => ( @@ -2368,8 +2370,9 @@ sub _split_version { sub normalized_version { my ( $self ) = @_; - my @version_parts = map { $_ || 0 } $self->_split_version( $self->version ); - my $result = sprintf('%d%02d%02d', @version_parts); + my $result = sprintf('%d%02d%02d', map { $_ || 0 } $self->major, + $self->minor, + $self->revision); PTDEBUG && _d($self->version, 'normalizes to', $result); return $result; } @@ -2403,10 +2406,13 @@ sub BUILDARGS { if ( @_ == 1 ) { my %args; if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { - my $dbh = $_[0]; - my $query; PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); - if ( eval { $query = $dbh->selectall_hashref(q) } ) { + my $dbh = $_[0]; + my $query = eval { + $dbh->selectall_arrayref(q, { Slice => {} }) + }; + if ( $query ) { + $query = { map { $_->{variable_name} => $_->{value} } @$query }; @args{@methods} = $self->_split_version($query->{version}); $args{flavor} = delete $query->{version_comment} if $query->{version_comment}; @@ -2415,7 +2421,8 @@ sub BUILDARGS { @args{@methods} = $self->_split_version($query); } else { - PTDEBUG && _d("Couldn't get the version from the dbh: $@"); + Carp::confess("Couldn't get the version from the dbh while " + . "creating a VersionParser object: $@"); } $args{innodb_version} = eval { $self->_innodb_version($dbh) }; } @@ -3262,10 +3269,6 @@ sub get_connected_slaves { my $show = "SHOW GRANTS FOR "; my $user = 'CURRENT_USER()'; - if ( VersionParser->new($dbh) < '4.1.2' ) { - $user = $dbh->selectrow_arrayref('SELECT USER()')->[0]; - $user =~ s/([^@]+)@(.+)/'$1'\@'$2'/; - } my $sql = $show . $user; PTDEBUG && _d($dbh, $sql); diff --git a/bin/pt-deadlock-logger b/bin/pt-deadlock-logger index c2017a2f..7c6cc4e7 100755 --- a/bin/pt-deadlock-logger +++ b/bin/pt-deadlock-logger @@ -1505,6 +1505,8 @@ use overload ( fallback => 1, ); +use Carp (); + our $VERSION = 0.01; has major => ( @@ -1559,8 +1561,9 @@ sub _split_version { sub normalized_version { my ( $self ) = @_; - my @version_parts = map { $_ || 0 } $self->_split_version( $self->version ); - my $result = sprintf('%d%02d%02d', @version_parts); + my $result = sprintf('%d%02d%02d', map { $_ || 0 } $self->major, + $self->minor, + $self->revision); PTDEBUG && _d($self->version, 'normalizes to', $result); return $result; } @@ -1594,10 +1597,13 @@ sub BUILDARGS { if ( @_ == 1 ) { my %args; if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { - my $dbh = $_[0]; - my $query; PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); - if ( eval { $query = $dbh->selectall_hashref(q) } ) { + my $dbh = $_[0]; + my $query = eval { + $dbh->selectall_arrayref(q, { Slice => {} }) + }; + if ( $query ) { + $query = { map { $_->{variable_name} => $_->{value} } @$query }; @args{@methods} = $self->_split_version($query->{version}); $args{flavor} = delete $query->{version_comment} if $query->{version_comment}; @@ -1606,7 +1612,8 @@ sub BUILDARGS { @args{@methods} = $self->_split_version($query); } else { - PTDEBUG && _d("Couldn't get the version from the dbh: $@"); + Carp::confess("Couldn't get the version from the dbh while " + . "creating a VersionParser object: $@"); } $args{innodb_version} = eval { $self->_innodb_version($dbh) }; } diff --git a/bin/pt-duplicate-key-checker b/bin/pt-duplicate-key-checker index 753703be..720b7652 100755 --- a/bin/pt-duplicate-key-checker +++ b/bin/pt-duplicate-key-checker @@ -481,6 +481,8 @@ use overload ( fallback => 1, ); +use Carp (); + our $VERSION = 0.01; has major => ( @@ -535,8 +537,9 @@ sub _split_version { sub normalized_version { my ( $self ) = @_; - my @version_parts = map { $_ || 0 } $self->_split_version( $self->version ); - my $result = sprintf('%d%02d%02d', @version_parts); + my $result = sprintf('%d%02d%02d', map { $_ || 0 } $self->major, + $self->minor, + $self->revision); PTDEBUG && _d($self->version, 'normalizes to', $result); return $result; } @@ -570,10 +573,13 @@ sub BUILDARGS { if ( @_ == 1 ) { my %args; if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { - my $dbh = $_[0]; - my $query; PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); - if ( eval { $query = $dbh->selectall_hashref(q) } ) { + my $dbh = $_[0]; + my $query = eval { + $dbh->selectall_arrayref(q, { Slice => {} }) + }; + if ( $query ) { + $query = { map { $_->{variable_name} => $_->{value} } @$query }; @args{@methods} = $self->_split_version($query->{version}); $args{flavor} = delete $query->{version_comment} if $query->{version_comment}; @@ -582,7 +588,8 @@ sub BUILDARGS { @args{@methods} = $self->_split_version($query); } else { - PTDEBUG && _d("Couldn't get the version from the dbh: $@"); + Carp::confess("Couldn't get the version from the dbh while " + . "creating a VersionParser object: $@"); } $args{innodb_version} = eval { $self->_innodb_version($dbh) }; } diff --git a/bin/pt-find b/bin/pt-find index a12527b6..d083d7db 100755 --- a/bin/pt-find +++ b/bin/pt-find @@ -1986,6 +1986,8 @@ use overload ( fallback => 1, ); +use Carp (); + our $VERSION = 0.01; has major => ( @@ -2040,8 +2042,9 @@ sub _split_version { sub normalized_version { my ( $self ) = @_; - my @version_parts = map { $_ || 0 } $self->_split_version( $self->version ); - my $result = sprintf('%d%02d%02d', @version_parts); + my $result = sprintf('%d%02d%02d', map { $_ || 0 } $self->major, + $self->minor, + $self->revision); PTDEBUG && _d($self->version, 'normalizes to', $result); return $result; } @@ -2075,10 +2078,13 @@ sub BUILDARGS { if ( @_ == 1 ) { my %args; if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { - my $dbh = $_[0]; - my $query; PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); - if ( eval { $query = $dbh->selectall_hashref(q) } ) { + my $dbh = $_[0]; + my $query = eval { + $dbh->selectall_arrayref(q, { Slice => {} }) + }; + if ( $query ) { + $query = { map { $_->{variable_name} => $_->{value} } @$query }; @args{@methods} = $self->_split_version($query->{version}); $args{flavor} = delete $query->{version_comment} if $query->{version_comment}; @@ -2087,7 +2093,8 @@ sub BUILDARGS { @args{@methods} = $self->_split_version($query); } else { - PTDEBUG && _d("Couldn't get the version from the dbh: $@"); + Carp::confess("Couldn't get the version from the dbh while " + . "creating a VersionParser object: $@"); } $args{innodb_version} = eval { $self->_innodb_version($dbh) }; } diff --git a/bin/pt-heartbeat b/bin/pt-heartbeat index b3a17667..442b326b 100755 --- a/bin/pt-heartbeat +++ b/bin/pt-heartbeat @@ -222,10 +222,6 @@ sub get_connected_slaves { my $show = "SHOW GRANTS FOR "; my $user = 'CURRENT_USER()'; - if ( VersionParser->new($dbh) < '4.1.2' ) { - $user = $dbh->selectrow_arrayref('SELECT USER()')->[0]; - $user =~ s/([^@]+)@(.+)/'$1'\@'$2'/; - } my $sql = $show . $user; PTDEBUG && _d($dbh, $sql); @@ -1740,456 +1736,6 @@ if ( PTDEBUG ) { # End OptionParser package # ########################################################################### -# ########################################################################### -# Mo 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/Mo.pm -# t/lib/Mo.t -# See https://launchpad.net/percona-toolkit for more information. -# ########################################################################### -{ -BEGIN { -$INC{"Mo.pm"} = __FILE__; -package Mo; -our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. - -{ - no strict 'refs'; - sub _glob_for { - return \*{shift()} - } - - sub _stash_for { - return \%{ shift() . "::" }; - } -} - -use strict; -use warnings qw( FATAL all ); - -use Carp (); -use Scalar::Util (); - -our %TYPES = ( - Bool => sub { !$_[0] || (defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == 1) }, - Num => sub { defined $_[0] && &Scalar::Util::looks_like_number }, - Int => sub { defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == int $_[0] }, - Str => sub { defined $_[0] }, - Object => sub { defined $_[0] && &Scalar::Util::blessed }, - FileHandle => sub { local $@; require IO::Handle; fileno($_[0]) && $_[0]->opened }, - - map { - my $type = /R/ ? $_ : uc $_; - $_ . "Ref" => sub { ref $_[0] eq $type } - } qw(Array Code Hash Regexp Glob Scalar) -); - -our %metadata_for; -{ - package Mo::Object; - - sub new { - my $class = shift; - my $args = $class->BUILDARGS(@_); - - my @args_to_delete; - while ( my ($attr, $meta) = each %{$metadata_for{$class}} ) { - next unless exists $meta->{init_arg}; - my $init_arg = $meta->{init_arg}; - - if ( defined $init_arg ) { - $args->{$attr} = delete $args->{$init_arg}; - } - else { - push @args_to_delete, $attr; - } - } - - delete $args->{$_} for @args_to_delete; - - for my $attribute ( keys %$args ) { - if ( my $coerce = $metadata_for{$class}{$attribute}{coerce} ) { - $args->{$attribute} = $coerce->($args->{$attribute}); - } - if ( my $I = $metadata_for{$class}{$attribute}{isa} ) { - ( (my $I_name), $I ) = @{$I}; - Mo::_check_type_constaints($attribute, $I, $I_name, $args->{$attribute}); - } - } - - while ( my ($attribute, $meta) = each %{$metadata_for{$class}} ) { - next unless $meta->{required}; - Carp::confess("Attribute ($attribute) is required for $class") - if ! exists $args->{$attribute} - } - - @_ = %$args; - my $self = bless $args, $class; - - my @build_subs; - my $linearized_isa = mro::get_linear_isa($class); - - for my $isa_class ( @$linearized_isa ) { - unshift @build_subs, *{ Mo::_glob_for "${isa_class}::BUILD" }{CODE}; - } - exists &$_ && $_->( $self, @_ ) for grep { defined } @build_subs; - return $self; - } - - sub BUILDARGS { - shift; - my $ref; - if ( @_ == 1 && ref($_[0]) ) { - Carp::confess("Single parameters to new() must be a HASH ref") - unless ref($_[0]) eq ref({}); - $ref = {%{$_[0]}} # We want a new reference, always - } - else { - $ref = { @_ }; - } - return $ref; - } -} - -my %export_for; -sub Mo::import { - warnings->import(qw(FATAL all)); - strict->import(); - - my $caller = scalar caller(); # Caller's package - my $caller_pkg = $caller . "::"; # Caller's package with :: at the end - my (%exports, %options); - - my (undef, @features) = @_; - my %ignore = ( map { $_ => 1 } qw( is isa init_arg builder buildargs clearer predicate build handles default required ) ); - for my $feature (grep { !$ignore{$_} } @features) { - { local $@; require "Mo/$feature.pm"; } - { - no strict 'refs'; - &{"Mo::${feature}::e"}( - $caller_pkg, - \%exports, - \%options, - \@_ - ); - } - } - - return if $exports{M}; - - %exports = ( - extends => sub { - for my $class ( map { "$_" } @_ ) { - $class =~ s{::|'}{/}g; - { local $@; eval { require "$class.pm" } } # or warn $@; - } - _set_package_isa($caller, @_); - _set_inherited_metadata($caller); - }, - has => sub { - my $names = shift; - for my $attribute ( ref $names ? @$names : $names ) { - my %args = @_; - my $method = ($args{is} || '') eq 'ro' - ? sub { - Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller_pkg}${attribute}") - if $#_; - return $_[0]{$attribute}; - } - : sub { - return $#_ - ? $_[0]{$attribute} = $_[1] - : $_[0]{$attribute}; - }; - - $metadata_for{$caller}{$attribute} = (); - - if ( my $I = $args{isa} ) { - my $orig_I = $I; - my $type; - if ( $I =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { - $I = _nested_constraints($attribute, $1, $2); - } - $metadata_for{$caller}{$attribute}{isa} = [$orig_I, $I]; - my $orig_method = $method; - $method = sub { - if ( $#_ ) { - Mo::_check_type_constaints($attribute, $I, $orig_I, $_[1]); - } - goto &$orig_method; - }; - } - - if ( my $builder = $args{builder} ) { - my $original_method = $method; - $method = sub { - $#_ - ? goto &$original_method - : ! exists $_[0]{$attribute} - ? $_[0]{$attribute} = $_[0]->$builder - : goto &$original_method - }; - } - - if ( my $code = $args{default} ) { - Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") - unless ref($code) eq 'CODE'; - my $original_method = $method; - $method = sub { - $#_ - ? goto &$original_method - : ! exists $_[0]{$attribute} - ? $_[0]{$attribute} = $_[0]->$code - : goto &$original_method - }; - } - - if ( my $role = $args{does} ) { - my $original_method = $method; - $method = sub { - if ( $#_ ) { - Carp::confess(qq) - unless blessed($_[1]) && $_[1]->does($role) - } - goto &$original_method - }; - } - - if ( my $coercion = $args{coerce} ) { - $metadata_for{$caller}{$attribute}{coerce} = $coercion; - my $original_method = $method; - $method = sub { - if ( $#_ ) { - return $original_method->($_[0], $coercion->($_[1])) - } - goto &$original_method; - } - } - - $method = $options{$_}->($method, $attribute, @_) - for sort keys %options; - - *{ _glob_for "${caller}::$attribute" } = $method; - - if ( $args{required} ) { - $metadata_for{$caller}{$attribute}{required} = 1; - } - - if ($args{clearer}) { - *{ _glob_for "${caller}::$args{clearer}" } - = sub { delete shift->{$attribute} } - } - - if ($args{predicate}) { - *{ _glob_for "${caller}::$args{predicate}" } - = sub { exists shift->{$attribute} } - } - - if ($args{handles}) { - _has_handles($caller, $attribute, \%args); - } - - if (exists $args{init_arg}) { - $metadata_for{$caller}{$attribute}{init_arg} = $args{init_arg}; - } - } - }, - %exports, - ); - - $export_for{$caller} = [ keys %exports ]; - - for my $keyword ( keys %exports ) { - *{ _glob_for "${caller}::$keyword" } = $exports{$keyword} - } - *{ _glob_for "${caller}::extends" }{CODE}->( "Mo::Object" ) - unless @{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] }; -}; - -sub _check_type_constaints { - my ($attribute, $I, $I_name, $val) = @_; - ( ref($I) eq 'CODE' - ? $I->($val) - : (ref $val eq $I - || ($val && $val eq $I) - || (exists $TYPES{$I} && $TYPES{$I}->($val))) - ) - || Carp::confess( - qq - . qq - . (defined $val ? Mo::Dumper($val) : 'undef') ) -} - -sub _has_handles { - my ($caller, $attribute, $args) = @_; - my $handles = $args->{handles}; - - my $ref = ref $handles; - my $kv; - if ( $ref eq ref [] ) { - $kv = { map { $_,$_ } @{$handles} }; - } - elsif ( $ref eq ref {} ) { - $kv = $handles; - } - elsif ( $ref eq ref qr// ) { - Carp::confess("Cannot delegate methods based on a Regexp without a type constraint (isa)") - unless $args->{isa}; - my $target_class = $args->{isa}; - $kv = { - map { $_, $_ } - grep { $_ =~ $handles } - grep { !exists $Mo::Object::{$_} && $target_class->can($_) } - grep { $_ ne 'has' && $_ ne 'extends' } - keys %{ _stash_for $target_class } - }; - } - else { - Carp::confess("handles for $ref not yet implemented"); - } - - while ( my ($method, $target) = each %{$kv} ) { - my $name = _glob_for "${caller}::$method"; - Carp::confess("You cannot overwrite a locally defined method ($method) with a delegation") - if defined &$name; - - my ($target, @curried_args) = ref($target) ? @$target : $target; - *$name = sub { - my $self = shift; - my $delegate_to = $self->$attribute(); - my $error = "Cannot delegate $method to $target because the value of $attribute"; - Carp::confess("$error is not defined") unless $delegate_to; - Carp::confess("$error is not an object (got '$delegate_to')") - unless Scalar::Util::blessed($delegate_to) || (!ref($delegate_to) && $delegate_to->can($target)); - return $delegate_to->$target(@curried_args, @_); - } - } -} - -sub _nested_constraints { - my ($attribute, $aggregate_type, $type) = @_; - - my $inner_types; - if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { - $inner_types = _nested_constraints($1, $2); - } - else { - $inner_types = $TYPES{$type}; - } - - if ( $aggregate_type eq 'ArrayRef' ) { - return sub { - my ($val) = @_; - return unless ref($val) eq ref([]); - - if ($inner_types) { - for my $value ( @{$val} ) { - return unless $inner_types->($value) - } - } - else { - for my $value ( @{$val} ) { - return unless $value && ($value eq $type - || (Scalar::Util::blessed($value) && $value->isa($type))); - } - } - return 1; - }; - } - elsif ( $aggregate_type eq 'Maybe' ) { - return sub { - my ($value) = @_; - return 1 if ! defined($value); - if ($inner_types) { - return unless $inner_types->($value) - } - else { - return unless $value eq $type - || (Scalar::Util::blessed($value) && $value->isa($type)); - } - return 1; - } - } - else { - Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); - } -} - -sub _set_package_isa { - my ($package, @new_isa) = @_; - - *{ _glob_for "${package}::ISA" } = [@new_isa]; -} - -sub _set_inherited_metadata { - my $class = shift; - my $linearized_isa = mro::get_linear_isa($class); - my %new_metadata; - - for my $isa_class (reverse @$linearized_isa) { - %new_metadata = ( - %new_metadata, - %{ $metadata_for{$isa_class} || {} }, - ); - } - $metadata_for{$class} = \%new_metadata; -} - -sub unimport { - my $caller = scalar caller(); - my $stash = _stash_for( $caller ); - - delete $stash->{$_} for @{$export_for{$caller}}; -} - -sub Dumper { - require Data::Dumper; - local $Data::Dumper::Indent = 0; - local $Data::Dumper::Sortkeys = 0; - local $Data::Dumper::Quotekeys = 0; - local $Data::Dumper::Terse = 1; - - Data::Dumper::Dumper(@_) -} - -BEGIN { - if ($] >= 5.010) { - { local $@; require mro; } - } - else { - local $@; - eval { - require MRO::Compat; - } or do { - *mro::get_linear_isa = *mro::get_linear_isa_dfs = sub { - no strict 'refs'; - - my $classname = shift; - - my @lin = ($classname); - my %stored; - foreach my $parent (@{"$classname\::ISA"}) { - my $plin = mro::get_linear_isa_dfs($parent); - foreach (@$plin) { - next if exists $stored{$_}; - push(@lin, $_); - $stored{$_} = 1; - } - } - return \@lin; - }; - } - } -} - -} -1; -} -# ########################################################################### -# End Mo package -# ########################################################################### - # ########################################################################### # DSNParser package # This package is a copy without comments from the original. The original @@ -2860,192 +2406,6 @@ sub deserialize_list { # End Quoter package # ########################################################################### -# ########################################################################### -# VersionParser 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/VersionParser.pm -# t/lib/VersionParser.t -# See https://launchpad.net/percona-toolkit for more information. -# ########################################################################### -{ -package VersionParser; - -use Mo; -use Scalar::Util qw(blessed); -use English qw(-no_match_vars); -use constant PTDEBUG => $ENV{PTDEBUG} || 0; - -use overload ( - '""' => "version", - '<=>' => "cmp", - 'cmp' => "cmp", - fallback => 1, -); - -our $VERSION = 0.01; - -has major => ( - is => 'ro', - isa => 'Int', - required => 1, -); - -has [qw( minor revision )] => ( - is => 'ro', - isa => 'Num', -); - -has flavor => ( - is => 'ro', - isa => 'Str', - default => sub { 'Unknown' }, -); - -has innodb_version => ( - is => 'ro', - isa => 'Str', - default => sub { 'NO' }, -); - -sub series { - my $self = shift; - return $self->_join_version($self->major, $self->minor); -} - -sub version { - my $self = shift; - return $self->_join_version($self->major, $self->minor, $self->revision); -} - -sub is_in { - my ($self, $target) = @_; - - return $self eq $target; -} - -sub _join_version { - my ($self, @parts) = @_; - - return join ".", map { my $c = $_; $c =~ s/^0\./0/; $c } grep defined, @parts; -} -sub _split_version { - my ($self, $str) = @_; - my @version_parts = map { s/^0(?=\d)/0./; $_ } $str =~ m/(\d+)/g; - return @version_parts[0..2]; -} - -sub normalized_version { - my ( $self ) = @_; - my @version_parts = map { $_ || 0 } $self->_split_version( $self->version ); - my $result = sprintf('%d%02d%02d', @version_parts); - PTDEBUG && _d($self->version, 'normalizes to', $result); - return $result; -} - -sub comment { - my ( $self, $cmd ) = @_; - my $v = $self->normalized_version(); - - return "/*!$v $cmd */" -} - -my @methods = qw(major minor revision); -sub cmp { - my ($left, $right) = @_; - my $right_obj = (blessed($right) && $right->isa(ref($left))) - ? $right - : ref($left)->new($right); - - my $retval = 0; - for my $m ( @methods ) { - last unless defined($left->$m) && defined($right_obj->$m); - $retval = $left->$m <=> $right_obj->$m; - last if $retval; - } - return $retval; -} - -sub BUILDARGS { - my $self = shift; - - if ( @_ == 1 ) { - my %args; - if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { - my $dbh = $_[0]; - my $query; - PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); - if ( eval { $query = $dbh->selectall_hashref(q) } ) { - @args{@methods} = $self->_split_version($query->{version}); - $args{flavor} = delete $query->{version_comment} - if $query->{version_comment}; - } - elsif ( eval { ($query) = $dbh->selectrow_array('SELECT VERSION()') } ) { - @args{@methods} = $self->_split_version($query); - } - else { - PTDEBUG && _d("Couldn't get the version from the dbh: $@"); - } - $args{innodb_version} = eval { $self->_innodb_version($dbh) }; - } - elsif ( !ref($_[0]) ) { - @args{@methods} = $self->_split_version($_[0]); - } - - for my $method (@methods) { - delete $args{$method} unless defined $args{$method}; - } - @_ = %args if %args; - } - - return $self->SUPER::BUILDARGS(@_); -} - -sub _innodb_version { - my ( $self, $dbh ) = @_; - return unless $dbh; - my $innodb_version = "NO"; - - my ($innodb) = - grep { $_->{engine} =~ m/InnoDB/i } - map { - my %hash; - @hash{ map { lc $_ } keys %$_ } = values %$_; - \%hash; - } - @{ $dbh->selectall_arrayref("SHOW ENGINES", {Slice=>{}}) }; - if ( $innodb ) { - PTDEBUG && _d("InnoDB support:", $innodb->{support}); - if ( $innodb->{support} =~ m/YES|DEFAULT/i ) { - my $vars = $dbh->selectrow_hashref( - "SHOW VARIABLES LIKE 'innodb_version'"); - $innodb_version = !$vars ? "BUILTIN" - : ($vars->{Value} || $vars->{value}); - } - else { - $innodb_version = $innodb->{support}; # probably DISABLED or NO - } - } - - PTDEBUG && _d("InnoDB version:", $innodb_version); - return $innodb_version; -} - -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"; -} - -no Mo; -1; -} -# ########################################################################### -# End VersionParser package -# ########################################################################### - # ########################################################################### # TableParser package # This package is a copy without comments from the original. The original diff --git a/bin/pt-index-usage b/bin/pt-index-usage index 07a23fdf..31dfa4f4 100755 --- a/bin/pt-index-usage +++ b/bin/pt-index-usage @@ -3970,6 +3970,8 @@ use overload ( fallback => 1, ); +use Carp (); + our $VERSION = 0.01; has major => ( @@ -4024,8 +4026,9 @@ sub _split_version { sub normalized_version { my ( $self ) = @_; - my @version_parts = map { $_ || 0 } $self->_split_version( $self->version ); - my $result = sprintf('%d%02d%02d', @version_parts); + my $result = sprintf('%d%02d%02d', map { $_ || 0 } $self->major, + $self->minor, + $self->revision); PTDEBUG && _d($self->version, 'normalizes to', $result); return $result; } @@ -4059,10 +4062,13 @@ sub BUILDARGS { if ( @_ == 1 ) { my %args; if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { - my $dbh = $_[0]; - my $query; PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); - if ( eval { $query = $dbh->selectall_hashref(q) } ) { + my $dbh = $_[0]; + my $query = eval { + $dbh->selectall_arrayref(q, { Slice => {} }) + }; + if ( $query ) { + $query = { map { $_->{variable_name} => $_->{value} } @$query }; @args{@methods} = $self->_split_version($query->{version}); $args{flavor} = delete $query->{version_comment} if $query->{version_comment}; @@ -4071,7 +4077,8 @@ sub BUILDARGS { @args{@methods} = $self->_split_version($query); } else { - PTDEBUG && _d("Couldn't get the version from the dbh: $@"); + Carp::confess("Couldn't get the version from the dbh while " + . "creating a VersionParser object: $@"); } $args{innodb_version} = eval { $self->_innodb_version($dbh) }; } diff --git a/bin/pt-kill b/bin/pt-kill index d2aca332..63b0abd4 100755 --- a/bin/pt-kill +++ b/bin/pt-kill @@ -3096,10 +3096,6 @@ sub get_connected_slaves { my $show = "SHOW GRANTS FOR "; my $user = 'CURRENT_USER()'; - if ( VersionParser->new($dbh) < '4.1.2' ) { - $user = $dbh->selectrow_arrayref('SELECT USER()')->[0]; - $user =~ s/([^@]+)@(.+)/'$1'\@'$2'/; - } my $sql = $show . $user; PTDEBUG && _d($dbh, $sql); diff --git a/bin/pt-online-schema-change b/bin/pt-online-schema-change index 94ce1329..dd7a4b07 100755 --- a/bin/pt-online-schema-change +++ b/bin/pt-online-schema-change @@ -1506,6 +1506,8 @@ use overload ( fallback => 1, ); +use Carp (); + our $VERSION = 0.01; has major => ( @@ -1560,8 +1562,9 @@ sub _split_version { sub normalized_version { my ( $self ) = @_; - my @version_parts = map { $_ || 0 } $self->_split_version( $self->version ); - my $result = sprintf('%d%02d%02d', @version_parts); + my $result = sprintf('%d%02d%02d', map { $_ || 0 } $self->major, + $self->minor, + $self->revision); PTDEBUG && _d($self->version, 'normalizes to', $result); return $result; } @@ -1595,10 +1598,13 @@ sub BUILDARGS { if ( @_ == 1 ) { my %args; if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { - my $dbh = $_[0]; - my $query; PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); - if ( eval { $query = $dbh->selectall_hashref(q) } ) { + my $dbh = $_[0]; + my $query = eval { + $dbh->selectall_arrayref(q, { Slice => {} }) + }; + if ( $query ) { + $query = { map { $_->{variable_name} => $_->{value} } @$query }; @args{@methods} = $self->_split_version($query->{version}); $args{flavor} = delete $query->{version_comment} if $query->{version_comment}; @@ -1607,7 +1613,8 @@ sub BUILDARGS { @args{@methods} = $self->_split_version($query); } else { - PTDEBUG && _d("Couldn't get the version from the dbh: $@"); + Carp::confess("Couldn't get the version from the dbh while " + . "creating a VersionParser object: $@"); } $args{innodb_version} = eval { $self->_innodb_version($dbh) }; } @@ -3632,10 +3639,6 @@ sub get_connected_slaves { my $show = "SHOW GRANTS FOR "; my $user = 'CURRENT_USER()'; - if ( VersionParser->new($dbh) < '4.1.2' ) { - $user = $dbh->selectrow_arrayref('SELECT USER()')->[0]; - $user =~ s/([^@]+)@(.+)/'$1'\@'$2'/; - } my $sql = $show . $user; PTDEBUG && _d($dbh, $sql); diff --git a/bin/pt-query-advisor b/bin/pt-query-advisor index 71d479b4..721606f9 100755 --- a/bin/pt-query-advisor +++ b/bin/pt-query-advisor @@ -5495,7 +5495,7 @@ sub get_keys { my ( $type, $cols ) = $key =~ m/(?:USING (\w+))? \((.+)\)/; my ( $special ) = $key =~ m/(FULLTEXT|SPATIAL)/; $type = $type || $special || 'BTREE'; - if ( $opts->{mysql_version} && $opts->{mysql_version} lt '004001000' + if ( $opts->{mysql_version} && $opts->{mysql_version} lt '4.1' && $engine =~ m/HEAP|MEMORY/i ) { $type = 'HASH'; # MySQL pre-4.1 supports only HASH indexes on HEAP diff --git a/bin/pt-query-digest b/bin/pt-query-digest index 6afdb8b2..688c8ecf 100755 --- a/bin/pt-query-digest +++ b/bin/pt-query-digest @@ -8296,7 +8296,7 @@ sub get_keys { my ( $type, $cols ) = $key =~ m/(?:USING (\w+))? \((.+)\)/; my ( $special ) = $key =~ m/(FULLTEXT|SPATIAL)/; $type = $type || $special || 'BTREE'; - if ( $opts->{mysql_version} && $opts->{mysql_version} lt '004001000' + if ( $opts->{mysql_version} && $opts->{mysql_version} lt '4.1' && $engine =~ m/HEAP|MEMORY/i ) { $type = 'HASH'; # MySQL pre-4.1 supports only HASH indexes on HEAP @@ -10533,11 +10533,6 @@ sub get_connected_slaves { my $show = "SHOW GRANTS FOR "; my $user = 'CURRENT_USER()'; - my $vp = $self->{VersionParser}; - if ( $vp && !$vp->version_ge($dbh, '4.1.2') ) { - $user = $dbh->selectrow_arrayref('SELECT USER()')->[0]; - $user =~ s/([^@]+)@(.+)/'$1'\@'$2'/; - } my $sql = $show . $user; PTDEBUG && _d($dbh, $sql); diff --git a/bin/pt-slave-find b/bin/pt-slave-find index 4fe482ee..aa60cd5d 100755 --- a/bin/pt-slave-find +++ b/bin/pt-slave-find @@ -2058,10 +2058,6 @@ sub get_connected_slaves { my $show = "SHOW GRANTS FOR "; my $user = 'CURRENT_USER()'; - if ( VersionParser->new($dbh) < '4.1.2' ) { - $user = $dbh->selectrow_arrayref('SELECT USER()')->[0]; - $user =~ s/([^@]+)@(.+)/'$1'\@'$2'/; - } my $sql = $show . $user; PTDEBUG && _d($dbh, $sql); @@ -2764,6 +2760,8 @@ use overload ( fallback => 1, ); +use Carp (); + our $VERSION = 0.01; has major => ( @@ -2818,8 +2816,9 @@ sub _split_version { sub normalized_version { my ( $self ) = @_; - my @version_parts = map { $_ || 0 } $self->_split_version( $self->version ); - my $result = sprintf('%d%02d%02d', @version_parts); + my $result = sprintf('%d%02d%02d', map { $_ || 0 } $self->major, + $self->minor, + $self->revision); PTDEBUG && _d($self->version, 'normalizes to', $result); return $result; } @@ -2853,10 +2852,13 @@ sub BUILDARGS { if ( @_ == 1 ) { my %args; if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { - my $dbh = $_[0]; - my $query; PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); - if ( eval { $query = $dbh->selectall_hashref(q) } ) { + my $dbh = $_[0]; + my $query = eval { + $dbh->selectall_arrayref(q, { Slice => {} }) + }; + if ( $query ) { + $query = { map { $_->{variable_name} => $_->{value} } @$query }; @args{@methods} = $self->_split_version($query->{version}); $args{flavor} = delete $query->{version_comment} if $query->{version_comment}; @@ -2865,7 +2867,8 @@ sub BUILDARGS { @args{@methods} = $self->_split_version($query); } else { - PTDEBUG && _d("Couldn't get the version from the dbh: $@"); + Carp::confess("Couldn't get the version from the dbh while " + . "creating a VersionParser object: $@"); } $args{innodb_version} = eval { $self->_innodb_version($dbh) }; } diff --git a/bin/pt-slave-restart b/bin/pt-slave-restart index 7b897e51..1047c32d 100755 --- a/bin/pt-slave-restart +++ b/bin/pt-slave-restart @@ -1625,6 +1625,8 @@ use overload ( fallback => 1, ); +use Carp (); + our $VERSION = 0.01; has major => ( @@ -1679,8 +1681,9 @@ sub _split_version { sub normalized_version { my ( $self ) = @_; - my @version_parts = map { $_ || 0 } $self->_split_version( $self->version ); - my $result = sprintf('%d%02d%02d', @version_parts); + my $result = sprintf('%d%02d%02d', map { $_ || 0 } $self->major, + $self->minor, + $self->revision); PTDEBUG && _d($self->version, 'normalizes to', $result); return $result; } @@ -1714,10 +1717,13 @@ sub BUILDARGS { if ( @_ == 1 ) { my %args; if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { - my $dbh = $_[0]; - my $query; PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); - if ( eval { $query = $dbh->selectall_hashref(q) } ) { + my $dbh = $_[0]; + my $query = eval { + $dbh->selectall_arrayref(q, { Slice => {} }) + }; + if ( $query ) { + $query = { map { $_->{variable_name} => $_->{value} } @$query }; @args{@methods} = $self->_split_version($query->{version}); $args{flavor} = delete $query->{version_comment} if $query->{version_comment}; @@ -1726,7 +1732,8 @@ sub BUILDARGS { @args{@methods} = $self->_split_version($query); } else { - PTDEBUG && _d("Couldn't get the version from the dbh: $@"); + Carp::confess("Couldn't get the version from the dbh while " + . "creating a VersionParser object: $@"); } $args{innodb_version} = eval { $self->_innodb_version($dbh) }; } @@ -2363,10 +2370,6 @@ sub get_connected_slaves { my $show = "SHOW GRANTS FOR "; my $user = 'CURRENT_USER()'; - if ( VersionParser->new($dbh) < '4.1.2' ) { - $user = $dbh->selectrow_arrayref('SELECT USER()')->[0]; - $user =~ s/([^@]+)@(.+)/'$1'\@'$2'/; - } my $sql = $show . $user; PTDEBUG && _d($dbh, $sql); diff --git a/bin/pt-table-checksum b/bin/pt-table-checksum index eceaf5d1..db2300da 100755 --- a/bin/pt-table-checksum +++ b/bin/pt-table-checksum @@ -2130,6 +2130,8 @@ use overload ( fallback => 1, ); +use Carp (); + our $VERSION = 0.01; has major => ( @@ -2184,8 +2186,9 @@ sub _split_version { sub normalized_version { my ( $self ) = @_; - my @version_parts = map { $_ || 0 } $self->_split_version( $self->version ); - my $result = sprintf('%d%02d%02d', @version_parts); + my $result = sprintf('%d%02d%02d', map { $_ || 0 } $self->major, + $self->minor, + $self->revision); PTDEBUG && _d($self->version, 'normalizes to', $result); return $result; } @@ -2219,10 +2222,13 @@ sub BUILDARGS { if ( @_ == 1 ) { my %args; if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { - my $dbh = $_[0]; - my $query; PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); - if ( eval { $query = $dbh->selectall_hashref(q) } ) { + my $dbh = $_[0]; + my $query = eval { + $dbh->selectall_arrayref(q, { Slice => {} }) + }; + if ( $query ) { + $query = { map { $_->{variable_name} => $_->{value} } @$query }; @args{@methods} = $self->_split_version($query->{version}); $args{flavor} = delete $query->{version_comment} if $query->{version_comment}; @@ -2231,7 +2237,8 @@ sub BUILDARGS { @args{@methods} = $self->_split_version($query); } else { - PTDEBUG && _d("Couldn't get the version from the dbh: $@"); + Carp::confess("Couldn't get the version from the dbh while " + . "creating a VersionParser object: $@"); } $args{innodb_version} = eval { $self->_innodb_version($dbh) }; } @@ -3216,10 +3223,6 @@ sub get_connected_slaves { my $show = "SHOW GRANTS FOR "; my $user = 'CURRENT_USER()'; - if ( VersionParser->new($dbh) < '4.1.2' ) { - $user = $dbh->selectrow_arrayref('SELECT USER()')->[0]; - $user =~ s/([^@]+)@(.+)/'$1'\@'$2'/; - } my $sql = $show . $user; PTDEBUG && _d($dbh, $sql); diff --git a/bin/pt-table-sync b/bin/pt-table-sync index cc7e013b..6038ea51 100755 --- a/bin/pt-table-sync +++ b/bin/pt-table-sync @@ -1986,6 +1986,8 @@ use overload ( fallback => 1, ); +use Carp (); + our $VERSION = 0.01; has major => ( @@ -2040,8 +2042,9 @@ sub _split_version { sub normalized_version { my ( $self ) = @_; - my @version_parts = map { $_ || 0 } $self->_split_version( $self->version ); - my $result = sprintf('%d%02d%02d', @version_parts); + my $result = sprintf('%d%02d%02d', map { $_ || 0 } $self->major, + $self->minor, + $self->revision); PTDEBUG && _d($self->version, 'normalizes to', $result); return $result; } @@ -2075,10 +2078,13 @@ sub BUILDARGS { if ( @_ == 1 ) { my %args; if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { - my $dbh = $_[0]; - my $query; PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); - if ( eval { $query = $dbh->selectall_hashref(q) } ) { + my $dbh = $_[0]; + my $query = eval { + $dbh->selectall_arrayref(q, { Slice => {} }) + }; + if ( $query ) { + $query = { map { $_->{variable_name} => $_->{value} } @$query }; @args{@methods} = $self->_split_version($query->{version}); $args{flavor} = delete $query->{version_comment} if $query->{version_comment}; @@ -2087,7 +2093,8 @@ sub BUILDARGS { @args{@methods} = $self->_split_version($query); } else { - PTDEBUG && _d("Couldn't get the version from the dbh: $@"); + Carp::confess("Couldn't get the version from the dbh while " + . "creating a VersionParser object: $@"); } $args{innodb_version} = eval { $self->_innodb_version($dbh) }; } @@ -6650,10 +6657,6 @@ sub get_connected_slaves { my $show = "SHOW GRANTS FOR "; my $user = 'CURRENT_USER()'; - if ( VersionParser->new($dbh) < '4.1.2' ) { - $user = $dbh->selectrow_arrayref('SELECT USER()')->[0]; - $user =~ s/([^@]+)@(.+)/'$1'\@'$2'/; - } my $sql = $show . $user; PTDEBUG && _d($dbh, $sql); diff --git a/bin/pt-table-usage b/bin/pt-table-usage index 121cd1e8..5204edad 100755 --- a/bin/pt-table-usage +++ b/bin/pt-table-usage @@ -5868,7 +5868,7 @@ sub get_keys { my ( $type, $cols ) = $key =~ m/(?:USING (\w+))? \((.+)\)/; my ( $special ) = $key =~ m/(FULLTEXT|SPATIAL)/; $type = $type || $special || 'BTREE'; - if ( $opts->{mysql_version} && $opts->{mysql_version} lt '004001000' + if ( $opts->{mysql_version} && $opts->{mysql_version} lt '4.1' && $engine =~ m/HEAP|MEMORY/i ) { $type = 'HASH'; # MySQL pre-4.1 supports only HASH indexes on HEAP diff --git a/bin/pt-upgrade b/bin/pt-upgrade index b1cb2986..f87ea877 100755 --- a/bin/pt-upgrade +++ b/bin/pt-upgrade @@ -8583,6 +8583,8 @@ use overload ( fallback => 1, ); +use Carp (); + our $VERSION = 0.01; has major => ( @@ -8637,8 +8639,9 @@ sub _split_version { sub normalized_version { my ( $self ) = @_; - my @version_parts = map { $_ || 0 } $self->_split_version( $self->version ); - my $result = sprintf('%d%02d%02d', @version_parts); + my $result = sprintf('%d%02d%02d', map { $_ || 0 } $self->major, + $self->minor, + $self->revision); PTDEBUG && _d($self->version, 'normalizes to', $result); return $result; } @@ -8672,10 +8675,13 @@ sub BUILDARGS { if ( @_ == 1 ) { my %args; if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { - my $dbh = $_[0]; - my $query; PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); - if ( eval { $query = $dbh->selectall_hashref(q) } ) { + my $dbh = $_[0]; + my $query = eval { + $dbh->selectall_arrayref(q, { Slice => {} }) + }; + if ( $query ) { + $query = { map { $_->{variable_name} => $_->{value} } @$query }; @args{@methods} = $self->_split_version($query->{version}); $args{flavor} = delete $query->{version_comment} if $query->{version_comment}; @@ -8684,7 +8690,8 @@ sub BUILDARGS { @args{@methods} = $self->_split_version($query); } else { - PTDEBUG && _d("Couldn't get the version from the dbh: $@"); + Carp::confess("Couldn't get the version from the dbh while " + . "creating a VersionParser object: $@"); } $args{innodb_version} = eval { $self->_innodb_version($dbh) }; } diff --git a/bin/pt-variable-advisor b/bin/pt-variable-advisor index 270b6c28..d1de38b2 100755 --- a/bin/pt-variable-advisor +++ b/bin/pt-variable-advisor @@ -1867,6 +1867,8 @@ use overload ( fallback => 1, ); +use Carp (); + our $VERSION = 0.01; has major => ( @@ -1921,8 +1923,9 @@ sub _split_version { sub normalized_version { my ( $self ) = @_; - my @version_parts = map { $_ || 0 } $self->_split_version( $self->version ); - my $result = sprintf('%d%02d%02d', @version_parts); + my $result = sprintf('%d%02d%02d', map { $_ || 0 } $self->major, + $self->minor, + $self->revision); PTDEBUG && _d($self->version, 'normalizes to', $result); return $result; } @@ -1956,10 +1959,13 @@ sub BUILDARGS { if ( @_ == 1 ) { my %args; if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { - my $dbh = $_[0]; - my $query; PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); - if ( eval { $query = $dbh->selectall_hashref(q) } ) { + my $dbh = $_[0]; + my $query = eval { + $dbh->selectall_arrayref(q, { Slice => {} }) + }; + if ( $query ) { + $query = { map { $_->{variable_name} => $_->{value} } @$query }; @args{@methods} = $self->_split_version($query->{version}); $args{flavor} = delete $query->{version_comment} if $query->{version_comment}; @@ -1968,7 +1974,8 @@ sub BUILDARGS { @args{@methods} = $self->_split_version($query); } else { - PTDEBUG && _d("Couldn't get the version from the dbh: $@"); + Carp::confess("Couldn't get the version from the dbh while " + . "creating a VersionParser object: $@"); } $args{innodb_version} = eval { $self->_innodb_version($dbh) }; } From 01f64ad72477eca6b70b2abaf7f0a879ea9a8e3d Mon Sep 17 00:00:00 2001 From: Brian Fraser Date: Fri, 13 Jul 2012 01:52:59 -0300 Subject: [PATCH 16/27] Make VersionParser temporarily set NAME_lc if using a dbh --- lib/VersionParser.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/VersionParser.pm b/lib/VersionParser.pm index 0262db2f..14ca9cf6 100644 --- a/lib/VersionParser.pm +++ b/lib/VersionParser.pm @@ -137,6 +137,7 @@ sub BUILDARGS { if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); my $dbh = $_[0]; + local $dbh->{FetchHashKeyName} = 'NAME_lc'; my $query = eval { $dbh->selectall_arrayref(q, { Slice => {} }) }; From 7f18bae25330c6dc7365dbf875cb7bb0cfbda183 Mon Sep 17 00:00:00 2001 From: Brian Fraser Date: Fri, 13 Jul 2012 01:54:09 -0300 Subject: [PATCH 17/27] Updated modules --- bin/pt-archiver | 1 + bin/pt-deadlock-logger | 1 + bin/pt-duplicate-key-checker | 1 + bin/pt-find | 1 + bin/pt-index-usage | 1 + bin/pt-online-schema-change | 1 + bin/pt-slave-find | 1 + bin/pt-slave-restart | 1 + bin/pt-table-checksum | 1 + bin/pt-table-sync | 1 + bin/pt-upgrade | 1 + bin/pt-variable-advisor | 1 + 12 files changed, 12 insertions(+) diff --git a/bin/pt-archiver b/bin/pt-archiver index 3bade8ee..ffad2fe3 100755 --- a/bin/pt-archiver +++ b/bin/pt-archiver @@ -2408,6 +2408,7 @@ sub BUILDARGS { if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); my $dbh = $_[0]; + local $dbh->{FetchHashKeyName} = 'NAME_lc'; my $query = eval { $dbh->selectall_arrayref(q, { Slice => {} }) }; diff --git a/bin/pt-deadlock-logger b/bin/pt-deadlock-logger index 7c6cc4e7..25d60d17 100755 --- a/bin/pt-deadlock-logger +++ b/bin/pt-deadlock-logger @@ -1599,6 +1599,7 @@ sub BUILDARGS { if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); my $dbh = $_[0]; + local $dbh->{FetchHashKeyName} = 'NAME_lc'; my $query = eval { $dbh->selectall_arrayref(q, { Slice => {} }) }; diff --git a/bin/pt-duplicate-key-checker b/bin/pt-duplicate-key-checker index 720b7652..b11bede9 100755 --- a/bin/pt-duplicate-key-checker +++ b/bin/pt-duplicate-key-checker @@ -575,6 +575,7 @@ sub BUILDARGS { if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); my $dbh = $_[0]; + local $dbh->{FetchHashKeyName} = 'NAME_lc'; my $query = eval { $dbh->selectall_arrayref(q, { Slice => {} }) }; diff --git a/bin/pt-find b/bin/pt-find index d083d7db..81f4e2a5 100755 --- a/bin/pt-find +++ b/bin/pt-find @@ -2080,6 +2080,7 @@ sub BUILDARGS { if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); my $dbh = $_[0]; + local $dbh->{FetchHashKeyName} = 'NAME_lc'; my $query = eval { $dbh->selectall_arrayref(q, { Slice => {} }) }; diff --git a/bin/pt-index-usage b/bin/pt-index-usage index 31dfa4f4..625f831d 100755 --- a/bin/pt-index-usage +++ b/bin/pt-index-usage @@ -4064,6 +4064,7 @@ sub BUILDARGS { if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); my $dbh = $_[0]; + local $dbh->{FetchHashKeyName} = 'NAME_lc'; my $query = eval { $dbh->selectall_arrayref(q, { Slice => {} }) }; diff --git a/bin/pt-online-schema-change b/bin/pt-online-schema-change index dd7a4b07..2c25373f 100755 --- a/bin/pt-online-schema-change +++ b/bin/pt-online-schema-change @@ -1600,6 +1600,7 @@ sub BUILDARGS { if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); my $dbh = $_[0]; + local $dbh->{FetchHashKeyName} = 'NAME_lc'; my $query = eval { $dbh->selectall_arrayref(q, { Slice => {} }) }; diff --git a/bin/pt-slave-find b/bin/pt-slave-find index aa60cd5d..f55a9a67 100755 --- a/bin/pt-slave-find +++ b/bin/pt-slave-find @@ -2854,6 +2854,7 @@ sub BUILDARGS { if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); my $dbh = $_[0]; + local $dbh->{FetchHashKeyName} = 'NAME_lc'; my $query = eval { $dbh->selectall_arrayref(q, { Slice => {} }) }; diff --git a/bin/pt-slave-restart b/bin/pt-slave-restart index 1047c32d..90ae54ef 100755 --- a/bin/pt-slave-restart +++ b/bin/pt-slave-restart @@ -1719,6 +1719,7 @@ sub BUILDARGS { if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); my $dbh = $_[0]; + local $dbh->{FetchHashKeyName} = 'NAME_lc'; my $query = eval { $dbh->selectall_arrayref(q, { Slice => {} }) }; diff --git a/bin/pt-table-checksum b/bin/pt-table-checksum index db2300da..c44c7ffc 100755 --- a/bin/pt-table-checksum +++ b/bin/pt-table-checksum @@ -2224,6 +2224,7 @@ sub BUILDARGS { if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); my $dbh = $_[0]; + local $dbh->{FetchHashKeyName} = 'NAME_lc'; my $query = eval { $dbh->selectall_arrayref(q, { Slice => {} }) }; diff --git a/bin/pt-table-sync b/bin/pt-table-sync index 6038ea51..c97437a5 100755 --- a/bin/pt-table-sync +++ b/bin/pt-table-sync @@ -2080,6 +2080,7 @@ sub BUILDARGS { if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); my $dbh = $_[0]; + local $dbh->{FetchHashKeyName} = 'NAME_lc'; my $query = eval { $dbh->selectall_arrayref(q, { Slice => {} }) }; diff --git a/bin/pt-upgrade b/bin/pt-upgrade index f87ea877..f8ee43d2 100755 --- a/bin/pt-upgrade +++ b/bin/pt-upgrade @@ -8677,6 +8677,7 @@ sub BUILDARGS { if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); my $dbh = $_[0]; + local $dbh->{FetchHashKeyName} = 'NAME_lc'; my $query = eval { $dbh->selectall_arrayref(q, { Slice => {} }) }; diff --git a/bin/pt-variable-advisor b/bin/pt-variable-advisor index d1de38b2..8d6a9670 100755 --- a/bin/pt-variable-advisor +++ b/bin/pt-variable-advisor @@ -1961,6 +1961,7 @@ sub BUILDARGS { if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); my $dbh = $_[0]; + local $dbh->{FetchHashKeyName} = 'NAME_lc'; my $query = eval { $dbh->selectall_arrayref(q, { Slice => {} }) }; From 815b4ce961fd15bbc1105a0ddba50c1b3c09f356 Mon Sep 17 00:00:00 2001 From: Brian Fraser Date: Fri, 13 Jul 2012 03:40:17 -0300 Subject: [PATCH 18/27] Fix the test plan for t/lib/VersionParser.t --- t/lib/VersionParser.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/lib/VersionParser.t b/t/lib/VersionParser.t index 70a27b25..15343297 100644 --- a/t/lib/VersionParser.t +++ b/t/lib/VersionParser.t @@ -9,7 +9,7 @@ BEGIN { use strict; use warnings FATAL => 'all'; use English qw(-no_match_vars); -use Test::More tests => 29; +use Test::More tests => 31; use VersionParser; use PerconaTest; From ade6357157f7b00e51d24f4d5d2d923b3dc94130 Mon Sep 17 00:00:00 2001 From: Brian Fraser Date: Thu, 19 Jul 2012 11:45:43 -0300 Subject: [PATCH 19/27] Make all t/lib/Mo.pm tests use done_testing --- t/lib/Mo/coerce.t | 5 +++-- t/lib/Mo/extends.t | 4 +++- t/lib/Mo/handles.t | 4 +++- t/lib/Mo/is.t | 4 ++-- t/lib/Mo/isa.t | 4 +++- t/lib/Mo/object.t | 4 +++- t/lib/Mo/required.t | 4 ++-- t/lib/Mo/strict.t | 4 +++- t/lib/Mo/test.t | 4 ++-- 9 files changed, 24 insertions(+), 13 deletions(-) diff --git a/t/lib/Mo/coerce.t b/t/lib/Mo/coerce.t index 2a69fcfa..8af02b08 100644 --- a/t/lib/Mo/coerce.t +++ b/t/lib/Mo/coerce.t @@ -11,8 +11,6 @@ use warnings FATAL => 'all'; use English qw(-no_match_vars); use Test::More; -plan tests => 2; - package Foo::coerce; use Mo; @@ -24,3 +22,6 @@ my $f = Foo::coerce->new(stuff => 'fubar'); is $f->stuff, 'FUBAR', 'values passed to constructor are successfully coerced'; $f->stuff('barbaz'); is $f->stuff, 'BARBAZ', 'values passed to setters are successfully coerced'; + + +done_testing; diff --git a/t/lib/Mo/extends.t b/t/lib/Mo/extends.t index 4cc738d8..30eb8731 100644 --- a/t/lib/Mo/extends.t +++ b/t/lib/Mo/extends.t @@ -9,7 +9,7 @@ BEGIN { use strict; use warnings FATAL => 'all'; use English qw(-no_match_vars); -use Test::More tests => 4; +use Test::More; use lib "$ENV{PERCONA_TOOLKIT_BRANCH}/t/lib/Mo"; use Bar; @@ -22,3 +22,5 @@ is "@Bar::ISA", "Foo", 'Extends with multiple classes not supported'; ok 'Foo'->can('stuff'), 'Foo is loaded'; ok not('Bar'->can('buff')), 'Boo is not loaded'; + +done_testing; diff --git a/t/lib/Mo/handles.t b/t/lib/Mo/handles.t index 47a283b3..e8f74ccc 100644 --- a/t/lib/Mo/handles.t +++ b/t/lib/Mo/handles.t @@ -9,7 +9,7 @@ BEGIN { use strict; use warnings FATAL => 'all'; use English qw(-no_match_vars); -use Test::More tests => 82; +use Test::More; # ------------------------------------------------------------------- @@ -478,3 +478,5 @@ is($car->stop, 'Engine::stop', '... got the right value from ->stop'); ); } + +done_testing; diff --git a/t/lib/Mo/is.t b/t/lib/Mo/is.t index 154f86a4..84ea47d1 100644 --- a/t/lib/Mo/is.t +++ b/t/lib/Mo/is.t @@ -11,8 +11,6 @@ use warnings FATAL => 'all'; use English qw(-no_match_vars); use Test::More; -plan tests => 2; - package Foo::is; use Mo qw(is); @@ -24,3 +22,5 @@ my $f = Foo::is->new(stuff => 'foo'); is $f->stuff, 'foo', 'values passed to constructor are successfully accepted'; eval { $f->stuff('barbaz') }; ok $@, 'setting values after initialization throws an exception'; + +done_testing; diff --git a/t/lib/Mo/isa.t b/t/lib/Mo/isa.t index e90519dc..e6a56e8f 100644 --- a/t/lib/Mo/isa.t +++ b/t/lib/Mo/isa.t @@ -9,7 +9,7 @@ BEGIN { use strict; use warnings FATAL => 'all'; use English qw(-no_match_vars); -use Test::More tests => 80; +use Test::More; sub dies_ok (&;$) { my $code = shift; @@ -119,3 +119,5 @@ for my $i (4..7) { for my $type (@types[1..$#types]) { my $method = "my$type"; dies_ok { $foo->$method(undef) } "$type attr set to undef dies" } + +done_testing; diff --git a/t/lib/Mo/object.t b/t/lib/Mo/object.t index 9aee9a21..37a71ea4 100644 --- a/t/lib/Mo/object.t +++ b/t/lib/Mo/object.t @@ -9,10 +9,12 @@ BEGIN { use strict; use warnings FATAL => 'all'; use English qw(-no_match_vars); -use Test::More tests => 2; +use Test::More; use lib "$ENV{PERCONA_TOOLKIT_BRANCH}/t/lib/Mo"; { package Clean; use Foo; } is_deeply([ @Clean::ISA ], [], "Didn't mess with caller's ISA"); is(Clean->can('has'), undef, "Didn't export anything"); + +done_testing; diff --git a/t/lib/Mo/required.t b/t/lib/Mo/required.t index 569f46cb..b7319317 100644 --- a/t/lib/Mo/required.t +++ b/t/lib/Mo/required.t @@ -11,8 +11,6 @@ use warnings FATAL => 'all'; use English qw(-no_match_vars); use Test::More; -plan tests => 3; - #============ package Foo::required; use Mo qw(required); @@ -37,3 +35,5 @@ is $f->stuff, 'fubar', 'Object is correctly initialized when required values are my $f2 = Foo::required_is->new(stuff => 'fubar'); is $f2->stuff, 'fubar', 'Object is correctly initialized when required is combined with is'; + +done_testing; diff --git a/t/lib/Mo/strict.t b/t/lib/Mo/strict.t index 8669ca43..eb98e4dd 100644 --- a/t/lib/Mo/strict.t +++ b/t/lib/Mo/strict.t @@ -9,9 +9,11 @@ BEGIN { use strict; use warnings FATAL => 'all'; use English qw(-no_match_vars); -use Test::More tests => 1; +use Test::More; eval 'package Foo; use Mo; $x = 1'; like $@, qr/Global symbol "\$x" requires explicit package name/, 'Mo is strict'; + +done_testing; diff --git a/t/lib/Mo/test.t b/t/lib/Mo/test.t index 80239654..87682a31 100644 --- a/t/lib/Mo/test.t +++ b/t/lib/Mo/test.t @@ -11,8 +11,6 @@ use warnings FATAL => 'all'; use English qw(-no_match_vars); use Test::More; -plan tests => 39; - #============ package Foo; use Mo; @@ -138,3 +136,5 @@ my $maz = Maz->new; is $_, 5, '$_ is untouched'; is $maz->foo, 5, 'BUILD works again'; is $maz->bar, 7, 'BUILD works in parent class'; + +done_testing; From cbded60b8c574bdb559119ca3369633311cd5c93 Mon Sep 17 00:00:00 2001 From: Brian Fraser Date: Thu, 19 Jul 2012 12:07:16 -0300 Subject: [PATCH 20/27] VersionParser.t: Add descriptions to three tests --- t/lib/VersionParser.t | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/t/lib/VersionParser.t b/t/lib/VersionParser.t index 15343297..525e4417 100644 --- a/t/lib/VersionParser.t +++ b/t/lib/VersionParser.t @@ -36,9 +36,9 @@ is( my $v2; $v2 = new_ok "VersionParser", [ qw( major 5 minor 5 revision 5 ) ], "new from parts works"; -is( "$v2", "5.5.5" ); +is( "$v2", "5.5.5", "..and stringifies correctly" ); $v2 = new_ok "VersionParser", [ { qw( major 5 minor 5 revision 5 ) } ], "new from hashref works"; -is( "$v2", "5.5.5" ); +is( "$v2", "5.5.5", "..and stringifies correctly" ); for my $test ( [ "5.0.1", "lt", "5.0.2" ], @@ -71,6 +71,7 @@ is( is( $c->comment('@@hostname,'), '/*!50501 @@hostname, */', + '->comment works with @@variable' ); From 02665e802e1593ef0e32bbf78680ace3db6f6ddd Mon Sep 17 00:00:00 2001 From: Brian Fraser Date: Thu, 19 Jul 2012 12:08:28 -0300 Subject: [PATCH 21/27] VersionParser.pm: Quote SQL with q// --- lib/VersionParser.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/VersionParser.pm b/lib/VersionParser.pm index 14ca9cf6..ea7e15e3 100644 --- a/lib/VersionParser.pm +++ b/lib/VersionParser.pm @@ -139,7 +139,7 @@ sub BUILDARGS { my $dbh = $_[0]; local $dbh->{FetchHashKeyName} = 'NAME_lc'; my $query = eval { - $dbh->selectall_arrayref(q, { Slice => {} }) + $dbh->selectall_arrayref(q/SHOW VARIABLES LIKE 'version%'/, { Slice => {} }) }; if ( $query ) { $query = { map { $_->{variable_name} => $_->{value} } @$query }; @@ -147,7 +147,7 @@ sub BUILDARGS { $args{flavor} = delete $query->{version_comment} if $query->{version_comment}; } - elsif ( eval { ($query) = $dbh->selectrow_array('SELECT VERSION()') } ) { + elsif ( eval { ($query) = $dbh->selectrow_array(q/SELECT VERSION()/) } ) { @args{@methods} = $self->_split_version($query); } else { From de625a873c5e852e040034bd8f1fd158a4c7f639 Mon Sep 17 00:00:00 2001 From: Brian Fraser Date: Thu, 19 Jul 2012 12:12:28 -0300 Subject: [PATCH 22/27] VersionParser.pm: Code comments --- lib/VersionParser.pm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/VersionParser.pm b/lib/VersionParser.pm index ea7e15e3..4dace928 100644 --- a/lib/VersionParser.pm +++ b/lib/VersionParser.pm @@ -79,6 +79,9 @@ sub is_in { } # Internal +# The crux of these two versions is to transform a version like 5.1.01 into +# 5, 1, and 0.1, and then reverse the process. This is so that the version +# above and 5.1.1 are differentiated. sub _join_version { my ($self, @parts) = @_; @@ -88,12 +91,11 @@ sub _join_version { sub _split_version { my ($self, $str) = @_; my @version_parts = map { s/^0(?=\d)/0./; $_ } $str =~ m/(\d+)/g; - # Turn a version like 5.5 into 5.5.0 return @version_parts[0..2]; } # Returns the version formatted as %d%02d%02d; that is, 5.1.20 would become -# 50120 +# 50120, 5.1.2 would become 50102, and 5.1.02 would become 50100 sub normalized_version { my ( $self ) = @_; my $result = sprintf('%d%02d%02d', map { $_ || 0 } $self->major, From 64c14e18f45a06825288e85e92706772b16ef56f Mon Sep 17 00:00:00 2001 From: Brian Fraser Date: Thu, 19 Jul 2012 13:19:26 -0300 Subject: [PATCH 23/27] Remove MySQL 4.0 checks from several libraries, losing the VP dependency --- lib/TableChecksum.pm | 10 +--------- lib/TableParser.pm | 8 -------- lib/TableSyncer.pm | 7 ++----- t/lib/TableChecksum.t | 9 ++++----- 4 files changed, 7 insertions(+), 27 deletions(-) diff --git a/lib/TableChecksum.pm b/lib/TableChecksum.pm index 714e7de0..2b7206c0 100644 --- a/lib/TableChecksum.pm +++ b/lib/TableChecksum.pm @@ -111,23 +111,15 @@ sub best_algorithm { die "Invalid checksum algorithm $alg" if $alg && !$ALGOS{$alg}; - my $version = VersionParser->new($dbh); - # CHECKSUM is eliminated by lots of things... if ( $args{where} || $args{chunk} # CHECKSUM does whole table - || $args{replicate} # CHECKSUM can't do INSERT.. SELECT - || $version < '4.1.1') # CHECKSUM doesn't exist + || $args{replicate}) # CHECKSUM can't do INSERT.. SELECT { PTDEBUG && _d('Cannot use CHECKSUM algorithm'); @choices = grep { $_ ne 'CHECKSUM' } @choices; } - # BIT_XOR isn't available till 4.1.1 either - if ( $version < '4.1.1' ) { - PTDEBUG && _d('Cannot use BIT_XOR algorithm because MySQL < 4.1.1'); - @choices = grep { $_ ne 'BIT_XOR' } @choices; - } # Choose the best (fastest) among the remaining choices. if ( $alg && grep { $_ eq $alg } @choices ) { diff --git a/lib/TableParser.pm b/lib/TableParser.pm index 1e711d48..9d90de3b 100644 --- a/lib/TableParser.pm +++ b/lib/TableParser.pm @@ -26,8 +26,6 @@ # $tbl is the return value from the sub below, parse(). # # And some subs have an optional $opts param which is a hashref of options. -# $opts->{mysql_version} is typically used, which is the return value from -# VersionParser->new() package TableParser; use strict; @@ -420,12 +418,6 @@ sub get_keys { my ( $type, $cols ) = $key =~ m/(?:USING (\w+))? \((.+)\)/; my ( $special ) = $key =~ m/(FULLTEXT|SPATIAL)/; $type = $type || $special || 'BTREE'; - if ( $opts->{mysql_version} && $opts->{mysql_version} lt '4.1' - && $engine =~ m/HEAP|MEMORY/i ) - { - $type = 'HASH'; # MySQL pre-4.1 supports only HASH indexes on HEAP - } - my ($name) = $key =~ m/(PRIMARY|`[^`]*`)/; my $unique = $key =~ m/PRIMARY|UNIQUE/ ? 1 : 0; my @cols; diff --git a/lib/TableSyncer.pm b/lib/TableSyncer.pm index 24f48083..30fa3de4 100644 --- a/lib/TableSyncer.pm +++ b/lib/TableSyncer.pm @@ -143,16 +143,13 @@ sub sync_table { # Make an index hint for either the explicitly given chunk_index # or the chunk_index chosen by the plugin if index_hint is true. my $index_hint; - my $hint = ((VersionParser->new($src->{dbh}) >= '4.0.9' - && VersionParser->new($dst->{dbh}) >= '4.0.9') ? 'FORCE' : 'USE') - . ' INDEX'; if ( $args{chunk_index} ) { PTDEBUG && _d('Using given chunk index for index hint'); - $index_hint = "$hint (" . $q->quote($args{chunk_index}) . ")"; + $index_hint = "FORCE INDEX (" . $q->quote($args{chunk_index}) . ")"; } elsif ( $plugin_args{chunk_index} && $args{index_hint} ) { PTDEBUG && _d('Using chunk index chosen by plugin for index hint'); - $index_hint = "$hint (" . $q->quote($plugin_args{chunk_index}) . ")"; + $index_hint = "FORCE INDEX (" . $q->quote($plugin_args{chunk_index}) . ")"; } PTDEBUG && _d('Index hint:', $index_hint); diff --git a/t/lib/TableChecksum.t b/t/lib/TableChecksum.t index 33f08d53..6d8d09ee 100644 --- a/t/lib/TableChecksum.t +++ b/t/lib/TableChecksum.t @@ -11,7 +11,6 @@ use warnings FATAL => 'all'; use English qw(-no_match_vars); use Test::More; -use VersionParser; use TableChecksum; use TableParser; use Quoter; @@ -118,8 +117,8 @@ is ( algorithm => 'CHECKSUM', dbh => '4.0.0', ), - 'ACCUM', - 'CHECKSUM and BIT_XOR eliminated by version', + 'CHECKSUM', + 'Ignore version, always use CHECKSUM', ); is ( @@ -136,8 +135,8 @@ is ( algorithm => 'BIT_XOR', dbh => '4.0.0', ), - 'ACCUM', - 'BIT_XOR eliminated by version', + 'BIT_XOR', + 'Ignore version, always use BIT_XOR', ); is ( From dfc271929cc1f95651a1d5c76a4ee0c976b63a29 Mon Sep 17 00:00:00 2001 From: Brian Fraser Date: Thu, 19 Jul 2012 13:20:45 -0300 Subject: [PATCH 24/27] Style consistency: Use numeric operators (<, ==, etc) for version checks --- lib/VariableAdvisorRules.pm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/VariableAdvisorRules.pm b/lib/VariableAdvisorRules.pm index 3905355e..703ed7fd 100644 --- a/lib/VariableAdvisorRules.pm +++ b/lib/VariableAdvisorRules.pm @@ -539,10 +539,10 @@ sub get_rules { my ( %args ) = @_; my $mysql_version = $args{mysql_version}; return 0 unless $mysql_version; - return 1 if ($mysql_version eq '3' && $mysql_version lt '3.23') - || ($mysql_version eq '4' && $mysql_version lt '4.1.20') - || ($mysql_version eq '5.0' && $mysql_version lt '5.0.37') - || ($mysql_version eq '5.1' && $mysql_version lt '5.1.30'); + return 1 if ($mysql_version == '3' && $mysql_version < '3.23' ) + || ($mysql_version == '4' && $mysql_version < '4.1.20') + || ($mysql_version == '5.0' && $mysql_version < '5.0.37') + || ($mysql_version == '5.1' && $mysql_version < '5.1.30'); return 0; }, }, @@ -552,7 +552,7 @@ sub get_rules { my ( %args ) = @_; my $mysql_version = $args{mysql_version}; return 0 unless $mysql_version; - return $mysql_version lt '5.1' ? 1 : 0; # 5.1.x + return $mysql_version < '5.1' ? 1 : 0; # 5.1.x }, }, }; From 81d541ec051812b77795354e551a35fab30052ce Mon Sep 17 00:00:00 2001 From: Brian Fraser Date: Thu, 19 Jul 2012 13:22:45 -0300 Subject: [PATCH 25/27] Update modules & cut the VP and Mo dependency from several tools --- bin/pt-archiver | 10 +- bin/pt-deadlock-logger | 4 +- bin/pt-duplicate-key-checker | 654 +-------------------------------- bin/pt-find | 655 +-------------------------------- bin/pt-heartbeat | 6 - bin/pt-index-usage | 653 +-------------------------------- bin/pt-online-schema-change | 10 +- bin/pt-query-advisor | 6 - bin/pt-query-digest | 6 - bin/pt-slave-find | 4 +- bin/pt-slave-restart | 4 +- bin/pt-table-checksum | 10 +- bin/pt-table-sync | 26 +- bin/pt-table-usage | 6 - bin/pt-upgrade | 690 +---------------------------------- bin/pt-variable-advisor | 14 +- 16 files changed, 36 insertions(+), 2722 deletions(-) diff --git a/bin/pt-archiver b/bin/pt-archiver index ffad2fe3..f0da577f 100755 --- a/bin/pt-archiver +++ b/bin/pt-archiver @@ -1787,12 +1787,6 @@ sub get_keys { my ( $type, $cols ) = $key =~ m/(?:USING (\w+))? \((.+)\)/; my ( $special ) = $key =~ m/(FULLTEXT|SPATIAL)/; $type = $type || $special || 'BTREE'; - if ( $opts->{mysql_version} && $opts->{mysql_version} lt '4.1' - && $engine =~ m/HEAP|MEMORY/i ) - { - $type = 'HASH'; # MySQL pre-4.1 supports only HASH indexes on HEAP - } - my ($name) = $key =~ m/(PRIMARY|`[^`]*`)/; my $unique = $key =~ m/PRIMARY|UNIQUE/ ? 1 : 0; my @cols; @@ -2410,7 +2404,7 @@ sub BUILDARGS { my $dbh = $_[0]; local $dbh->{FetchHashKeyName} = 'NAME_lc'; my $query = eval { - $dbh->selectall_arrayref(q, { Slice => {} }) + $dbh->selectall_arrayref(q/SHOW VARIABLES LIKE 'version%'/, { Slice => {} }) }; if ( $query ) { $query = { map { $_->{variable_name} => $_->{value} } @$query }; @@ -2418,7 +2412,7 @@ sub BUILDARGS { $args{flavor} = delete $query->{version_comment} if $query->{version_comment}; } - elsif ( eval { ($query) = $dbh->selectrow_array('SELECT VERSION()') } ) { + elsif ( eval { ($query) = $dbh->selectrow_array(q/SELECT VERSION()/) } ) { @args{@methods} = $self->_split_version($query); } else { diff --git a/bin/pt-deadlock-logger b/bin/pt-deadlock-logger index 25d60d17..6be99628 100755 --- a/bin/pt-deadlock-logger +++ b/bin/pt-deadlock-logger @@ -1601,7 +1601,7 @@ sub BUILDARGS { my $dbh = $_[0]; local $dbh->{FetchHashKeyName} = 'NAME_lc'; my $query = eval { - $dbh->selectall_arrayref(q, { Slice => {} }) + $dbh->selectall_arrayref(q/SHOW VARIABLES LIKE 'version%'/, { Slice => {} }) }; if ( $query ) { $query = { map { $_->{variable_name} => $_->{value} } @$query }; @@ -1609,7 +1609,7 @@ sub BUILDARGS { $args{flavor} = delete $query->{version_comment} if $query->{version_comment}; } - elsif ( eval { ($query) = $dbh->selectrow_array('SELECT VERSION()') } ) { + elsif ( eval { ($query) = $dbh->selectrow_array(q/SELECT VERSION()/) } ) { @args{@methods} = $self->_split_version($query); } else { diff --git a/bin/pt-duplicate-key-checker b/bin/pt-duplicate-key-checker index b11bede9..18da7e20 100755 --- a/bin/pt-duplicate-key-checker +++ b/bin/pt-duplicate-key-checker @@ -8,650 +8,6 @@ use strict; use warnings FATAL => 'all'; use constant PTDEBUG => $ENV{PTDEBUG} || 0; -# ########################################################################### -# Mo 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/Mo.pm -# t/lib/Mo.t -# See https://launchpad.net/percona-toolkit for more information. -# ########################################################################### -{ -BEGIN { -$INC{"Mo.pm"} = __FILE__; -package Mo; -our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. - -{ - no strict 'refs'; - sub _glob_for { - return \*{shift()} - } - - sub _stash_for { - return \%{ shift() . "::" }; - } -} - -use strict; -use warnings qw( FATAL all ); - -use Carp (); -use Scalar::Util (); - -our %TYPES = ( - Bool => sub { !$_[0] || (defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == 1) }, - Num => sub { defined $_[0] && &Scalar::Util::looks_like_number }, - Int => sub { defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == int $_[0] }, - Str => sub { defined $_[0] }, - Object => sub { defined $_[0] && &Scalar::Util::blessed }, - FileHandle => sub { local $@; require IO::Handle; fileno($_[0]) && $_[0]->opened }, - - map { - my $type = /R/ ? $_ : uc $_; - $_ . "Ref" => sub { ref $_[0] eq $type } - } qw(Array Code Hash Regexp Glob Scalar) -); - -our %metadata_for; -{ - package Mo::Object; - - sub new { - my $class = shift; - my $args = $class->BUILDARGS(@_); - - my @args_to_delete; - while ( my ($attr, $meta) = each %{$metadata_for{$class}} ) { - next unless exists $meta->{init_arg}; - my $init_arg = $meta->{init_arg}; - - if ( defined $init_arg ) { - $args->{$attr} = delete $args->{$init_arg}; - } - else { - push @args_to_delete, $attr; - } - } - - delete $args->{$_} for @args_to_delete; - - for my $attribute ( keys %$args ) { - if ( my $coerce = $metadata_for{$class}{$attribute}{coerce} ) { - $args->{$attribute} = $coerce->($args->{$attribute}); - } - if ( my $I = $metadata_for{$class}{$attribute}{isa} ) { - ( (my $I_name), $I ) = @{$I}; - Mo::_check_type_constaints($attribute, $I, $I_name, $args->{$attribute}); - } - } - - while ( my ($attribute, $meta) = each %{$metadata_for{$class}} ) { - next unless $meta->{required}; - Carp::confess("Attribute ($attribute) is required for $class") - if ! exists $args->{$attribute} - } - - @_ = %$args; - my $self = bless $args, $class; - - my @build_subs; - my $linearized_isa = mro::get_linear_isa($class); - - for my $isa_class ( @$linearized_isa ) { - unshift @build_subs, *{ Mo::_glob_for "${isa_class}::BUILD" }{CODE}; - } - exists &$_ && $_->( $self, @_ ) for grep { defined } @build_subs; - return $self; - } - - sub BUILDARGS { - shift; - my $ref; - if ( @_ == 1 && ref($_[0]) ) { - Carp::confess("Single parameters to new() must be a HASH ref") - unless ref($_[0]) eq ref({}); - $ref = {%{$_[0]}} # We want a new reference, always - } - else { - $ref = { @_ }; - } - return $ref; - } -} - -my %export_for; -sub Mo::import { - warnings->import(qw(FATAL all)); - strict->import(); - - my $caller = scalar caller(); # Caller's package - my $caller_pkg = $caller . "::"; # Caller's package with :: at the end - my (%exports, %options); - - my (undef, @features) = @_; - my %ignore = ( map { $_ => 1 } qw( is isa init_arg builder buildargs clearer predicate build handles default required ) ); - for my $feature (grep { !$ignore{$_} } @features) { - { local $@; require "Mo/$feature.pm"; } - { - no strict 'refs'; - &{"Mo::${feature}::e"}( - $caller_pkg, - \%exports, - \%options, - \@_ - ); - } - } - - return if $exports{M}; - - %exports = ( - extends => sub { - for my $class ( map { "$_" } @_ ) { - $class =~ s{::|'}{/}g; - { local $@; eval { require "$class.pm" } } # or warn $@; - } - _set_package_isa($caller, @_); - _set_inherited_metadata($caller); - }, - has => sub { - my $names = shift; - for my $attribute ( ref $names ? @$names : $names ) { - my %args = @_; - my $method = ($args{is} || '') eq 'ro' - ? sub { - Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller_pkg}${attribute}") - if $#_; - return $_[0]{$attribute}; - } - : sub { - return $#_ - ? $_[0]{$attribute} = $_[1] - : $_[0]{$attribute}; - }; - - $metadata_for{$caller}{$attribute} = (); - - if ( my $I = $args{isa} ) { - my $orig_I = $I; - my $type; - if ( $I =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { - $I = _nested_constraints($attribute, $1, $2); - } - $metadata_for{$caller}{$attribute}{isa} = [$orig_I, $I]; - my $orig_method = $method; - $method = sub { - if ( $#_ ) { - Mo::_check_type_constaints($attribute, $I, $orig_I, $_[1]); - } - goto &$orig_method; - }; - } - - if ( my $builder = $args{builder} ) { - my $original_method = $method; - $method = sub { - $#_ - ? goto &$original_method - : ! exists $_[0]{$attribute} - ? $_[0]{$attribute} = $_[0]->$builder - : goto &$original_method - }; - } - - if ( my $code = $args{default} ) { - Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") - unless ref($code) eq 'CODE'; - my $original_method = $method; - $method = sub { - $#_ - ? goto &$original_method - : ! exists $_[0]{$attribute} - ? $_[0]{$attribute} = $_[0]->$code - : goto &$original_method - }; - } - - if ( my $role = $args{does} ) { - my $original_method = $method; - $method = sub { - if ( $#_ ) { - Carp::confess(qq) - unless blessed($_[1]) && $_[1]->does($role) - } - goto &$original_method - }; - } - - if ( my $coercion = $args{coerce} ) { - $metadata_for{$caller}{$attribute}{coerce} = $coercion; - my $original_method = $method; - $method = sub { - if ( $#_ ) { - return $original_method->($_[0], $coercion->($_[1])) - } - goto &$original_method; - } - } - - $method = $options{$_}->($method, $attribute, @_) - for sort keys %options; - - *{ _glob_for "${caller}::$attribute" } = $method; - - if ( $args{required} ) { - $metadata_for{$caller}{$attribute}{required} = 1; - } - - if ($args{clearer}) { - *{ _glob_for "${caller}::$args{clearer}" } - = sub { delete shift->{$attribute} } - } - - if ($args{predicate}) { - *{ _glob_for "${caller}::$args{predicate}" } - = sub { exists shift->{$attribute} } - } - - if ($args{handles}) { - _has_handles($caller, $attribute, \%args); - } - - if (exists $args{init_arg}) { - $metadata_for{$caller}{$attribute}{init_arg} = $args{init_arg}; - } - } - }, - %exports, - ); - - $export_for{$caller} = [ keys %exports ]; - - for my $keyword ( keys %exports ) { - *{ _glob_for "${caller}::$keyword" } = $exports{$keyword} - } - *{ _glob_for "${caller}::extends" }{CODE}->( "Mo::Object" ) - unless @{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] }; -}; - -sub _check_type_constaints { - my ($attribute, $I, $I_name, $val) = @_; - ( ref($I) eq 'CODE' - ? $I->($val) - : (ref $val eq $I - || ($val && $val eq $I) - || (exists $TYPES{$I} && $TYPES{$I}->($val))) - ) - || Carp::confess( - qq - . qq - . (defined $val ? Mo::Dumper($val) : 'undef') ) -} - -sub _has_handles { - my ($caller, $attribute, $args) = @_; - my $handles = $args->{handles}; - - my $ref = ref $handles; - my $kv; - if ( $ref eq ref [] ) { - $kv = { map { $_,$_ } @{$handles} }; - } - elsif ( $ref eq ref {} ) { - $kv = $handles; - } - elsif ( $ref eq ref qr// ) { - Carp::confess("Cannot delegate methods based on a Regexp without a type constraint (isa)") - unless $args->{isa}; - my $target_class = $args->{isa}; - $kv = { - map { $_, $_ } - grep { $_ =~ $handles } - grep { !exists $Mo::Object::{$_} && $target_class->can($_) } - grep { $_ ne 'has' && $_ ne 'extends' } - keys %{ _stash_for $target_class } - }; - } - else { - Carp::confess("handles for $ref not yet implemented"); - } - - while ( my ($method, $target) = each %{$kv} ) { - my $name = _glob_for "${caller}::$method"; - Carp::confess("You cannot overwrite a locally defined method ($method) with a delegation") - if defined &$name; - - my ($target, @curried_args) = ref($target) ? @$target : $target; - *$name = sub { - my $self = shift; - my $delegate_to = $self->$attribute(); - my $error = "Cannot delegate $method to $target because the value of $attribute"; - Carp::confess("$error is not defined") unless $delegate_to; - Carp::confess("$error is not an object (got '$delegate_to')") - unless Scalar::Util::blessed($delegate_to) || (!ref($delegate_to) && $delegate_to->can($target)); - return $delegate_to->$target(@curried_args, @_); - } - } -} - -sub _nested_constraints { - my ($attribute, $aggregate_type, $type) = @_; - - my $inner_types; - if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { - $inner_types = _nested_constraints($1, $2); - } - else { - $inner_types = $TYPES{$type}; - } - - if ( $aggregate_type eq 'ArrayRef' ) { - return sub { - my ($val) = @_; - return unless ref($val) eq ref([]); - - if ($inner_types) { - for my $value ( @{$val} ) { - return unless $inner_types->($value) - } - } - else { - for my $value ( @{$val} ) { - return unless $value && ($value eq $type - || (Scalar::Util::blessed($value) && $value->isa($type))); - } - } - return 1; - }; - } - elsif ( $aggregate_type eq 'Maybe' ) { - return sub { - my ($value) = @_; - return 1 if ! defined($value); - if ($inner_types) { - return unless $inner_types->($value) - } - else { - return unless $value eq $type - || (Scalar::Util::blessed($value) && $value->isa($type)); - } - return 1; - } - } - else { - Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); - } -} - -sub _set_package_isa { - my ($package, @new_isa) = @_; - - *{ _glob_for "${package}::ISA" } = [@new_isa]; -} - -sub _set_inherited_metadata { - my $class = shift; - my $linearized_isa = mro::get_linear_isa($class); - my %new_metadata; - - for my $isa_class (reverse @$linearized_isa) { - %new_metadata = ( - %new_metadata, - %{ $metadata_for{$isa_class} || {} }, - ); - } - $metadata_for{$class} = \%new_metadata; -} - -sub unimport { - my $caller = scalar caller(); - my $stash = _stash_for( $caller ); - - delete $stash->{$_} for @{$export_for{$caller}}; -} - -sub Dumper { - require Data::Dumper; - local $Data::Dumper::Indent = 0; - local $Data::Dumper::Sortkeys = 0; - local $Data::Dumper::Quotekeys = 0; - local $Data::Dumper::Terse = 1; - - Data::Dumper::Dumper(@_) -} - -BEGIN { - if ($] >= 5.010) { - { local $@; require mro; } - } - else { - local $@; - eval { - require MRO::Compat; - } or do { - *mro::get_linear_isa = *mro::get_linear_isa_dfs = sub { - no strict 'refs'; - - my $classname = shift; - - my @lin = ($classname); - my %stored; - foreach my $parent (@{"$classname\::ISA"}) { - my $plin = mro::get_linear_isa_dfs($parent); - foreach (@$plin) { - next if exists $stored{$_}; - push(@lin, $_); - $stored{$_} = 1; - } - } - return \@lin; - }; - } - } -} - -} -1; -} -# ########################################################################### -# End Mo package -# ########################################################################### - -# ########################################################################### -# VersionParser 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/VersionParser.pm -# t/lib/VersionParser.t -# See https://launchpad.net/percona-toolkit for more information. -# ########################################################################### -{ -package VersionParser; - -use Mo; -use Scalar::Util qw(blessed); -use English qw(-no_match_vars); -use constant PTDEBUG => $ENV{PTDEBUG} || 0; - -use overload ( - '""' => "version", - '<=>' => "cmp", - 'cmp' => "cmp", - fallback => 1, -); - -use Carp (); - -our $VERSION = 0.01; - -has major => ( - is => 'ro', - isa => 'Int', - required => 1, -); - -has [qw( minor revision )] => ( - is => 'ro', - isa => 'Num', -); - -has flavor => ( - is => 'ro', - isa => 'Str', - default => sub { 'Unknown' }, -); - -has innodb_version => ( - is => 'ro', - isa => 'Str', - default => sub { 'NO' }, -); - -sub series { - my $self = shift; - return $self->_join_version($self->major, $self->minor); -} - -sub version { - my $self = shift; - return $self->_join_version($self->major, $self->minor, $self->revision); -} - -sub is_in { - my ($self, $target) = @_; - - return $self eq $target; -} - -sub _join_version { - my ($self, @parts) = @_; - - return join ".", map { my $c = $_; $c =~ s/^0\./0/; $c } grep defined, @parts; -} -sub _split_version { - my ($self, $str) = @_; - my @version_parts = map { s/^0(?=\d)/0./; $_ } $str =~ m/(\d+)/g; - return @version_parts[0..2]; -} - -sub normalized_version { - my ( $self ) = @_; - my $result = sprintf('%d%02d%02d', map { $_ || 0 } $self->major, - $self->minor, - $self->revision); - PTDEBUG && _d($self->version, 'normalizes to', $result); - return $result; -} - -sub comment { - my ( $self, $cmd ) = @_; - my $v = $self->normalized_version(); - - return "/*!$v $cmd */" -} - -my @methods = qw(major minor revision); -sub cmp { - my ($left, $right) = @_; - my $right_obj = (blessed($right) && $right->isa(ref($left))) - ? $right - : ref($left)->new($right); - - my $retval = 0; - for my $m ( @methods ) { - last unless defined($left->$m) && defined($right_obj->$m); - $retval = $left->$m <=> $right_obj->$m; - last if $retval; - } - return $retval; -} - -sub BUILDARGS { - my $self = shift; - - if ( @_ == 1 ) { - my %args; - if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { - PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); - my $dbh = $_[0]; - local $dbh->{FetchHashKeyName} = 'NAME_lc'; - my $query = eval { - $dbh->selectall_arrayref(q, { Slice => {} }) - }; - if ( $query ) { - $query = { map { $_->{variable_name} => $_->{value} } @$query }; - @args{@methods} = $self->_split_version($query->{version}); - $args{flavor} = delete $query->{version_comment} - if $query->{version_comment}; - } - elsif ( eval { ($query) = $dbh->selectrow_array('SELECT VERSION()') } ) { - @args{@methods} = $self->_split_version($query); - } - else { - Carp::confess("Couldn't get the version from the dbh while " - . "creating a VersionParser object: $@"); - } - $args{innodb_version} = eval { $self->_innodb_version($dbh) }; - } - elsif ( !ref($_[0]) ) { - @args{@methods} = $self->_split_version($_[0]); - } - - for my $method (@methods) { - delete $args{$method} unless defined $args{$method}; - } - @_ = %args if %args; - } - - return $self->SUPER::BUILDARGS(@_); -} - -sub _innodb_version { - my ( $self, $dbh ) = @_; - return unless $dbh; - my $innodb_version = "NO"; - - my ($innodb) = - grep { $_->{engine} =~ m/InnoDB/i } - map { - my %hash; - @hash{ map { lc $_ } keys %$_ } = values %$_; - \%hash; - } - @{ $dbh->selectall_arrayref("SHOW ENGINES", {Slice=>{}}) }; - if ( $innodb ) { - PTDEBUG && _d("InnoDB support:", $innodb->{support}); - if ( $innodb->{support} =~ m/YES|DEFAULT/i ) { - my $vars = $dbh->selectrow_hashref( - "SHOW VARIABLES LIKE 'innodb_version'"); - $innodb_version = !$vars ? "BUILTIN" - : ($vars->{Value} || $vars->{value}); - } - else { - $innodb_version = $innodb->{support}; # probably DISABLED or NO - } - } - - PTDEBUG && _d("InnoDB version:", $innodb_version); - return $innodb_version; -} - -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"; -} - -no Mo; -1; -} -# ########################################################################### -# End VersionParser package -# ########################################################################### - # ########################################################################### # Quoter package # This package is a copy without comments from the original. The original @@ -1075,12 +431,6 @@ sub get_keys { my ( $type, $cols ) = $key =~ m/(?:USING (\w+))? \((.+)\)/; my ( $special ) = $key =~ m/(FULLTEXT|SPATIAL)/; $type = $type || $special || 'BTREE'; - if ( $opts->{mysql_version} && $opts->{mysql_version} lt '4.1' - && $engine =~ m/HEAP|MEMORY/i ) - { - $type = 'HASH'; # MySQL pre-4.1 supports only HASH indexes on HEAP - } - my ($name) = $key =~ m/(PRIMARY|`[^`]*`)/; my $unique = $key =~ m/PRIMARY|UNIQUE/ ? 1 : 0; my @cols; @@ -4017,8 +3367,6 @@ sub main { my $dbh = $dp->get_dbh($dp->get_cxn_params($dsn), { AutoCommit => 1, }); - my $version = VersionParser->new($dbh); - # ####################################################################### # Do the main work. # ####################################################################### @@ -4049,7 +3397,7 @@ sub main { my ($keys, $clustered_key, $fks); if ( $get_keys ) { ($keys, $clustered_key) - = $tp->get_keys($tbl->{ddl}, {mysql_version => $version}); + = $tp->get_keys($tbl->{ddl}, {}); } if ( $get_fks ) { $fks = $tp->get_fks($tbl->{ddl}, {database => $tbl->{db}}); diff --git a/bin/pt-find b/bin/pt-find index 81f4e2a5..29617d1d 100755 --- a/bin/pt-find +++ b/bin/pt-find @@ -1394,456 +1394,6 @@ if ( PTDEBUG ) { # End OptionParser package # ########################################################################### -# ########################################################################### -# Mo 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/Mo.pm -# t/lib/Mo.t -# See https://launchpad.net/percona-toolkit for more information. -# ########################################################################### -{ -BEGIN { -$INC{"Mo.pm"} = __FILE__; -package Mo; -our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. - -{ - no strict 'refs'; - sub _glob_for { - return \*{shift()} - } - - sub _stash_for { - return \%{ shift() . "::" }; - } -} - -use strict; -use warnings qw( FATAL all ); - -use Carp (); -use Scalar::Util (); - -our %TYPES = ( - Bool => sub { !$_[0] || (defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == 1) }, - Num => sub { defined $_[0] && &Scalar::Util::looks_like_number }, - Int => sub { defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == int $_[0] }, - Str => sub { defined $_[0] }, - Object => sub { defined $_[0] && &Scalar::Util::blessed }, - FileHandle => sub { local $@; require IO::Handle; fileno($_[0]) && $_[0]->opened }, - - map { - my $type = /R/ ? $_ : uc $_; - $_ . "Ref" => sub { ref $_[0] eq $type } - } qw(Array Code Hash Regexp Glob Scalar) -); - -our %metadata_for; -{ - package Mo::Object; - - sub new { - my $class = shift; - my $args = $class->BUILDARGS(@_); - - my @args_to_delete; - while ( my ($attr, $meta) = each %{$metadata_for{$class}} ) { - next unless exists $meta->{init_arg}; - my $init_arg = $meta->{init_arg}; - - if ( defined $init_arg ) { - $args->{$attr} = delete $args->{$init_arg}; - } - else { - push @args_to_delete, $attr; - } - } - - delete $args->{$_} for @args_to_delete; - - for my $attribute ( keys %$args ) { - if ( my $coerce = $metadata_for{$class}{$attribute}{coerce} ) { - $args->{$attribute} = $coerce->($args->{$attribute}); - } - if ( my $I = $metadata_for{$class}{$attribute}{isa} ) { - ( (my $I_name), $I ) = @{$I}; - Mo::_check_type_constaints($attribute, $I, $I_name, $args->{$attribute}); - } - } - - while ( my ($attribute, $meta) = each %{$metadata_for{$class}} ) { - next unless $meta->{required}; - Carp::confess("Attribute ($attribute) is required for $class") - if ! exists $args->{$attribute} - } - - @_ = %$args; - my $self = bless $args, $class; - - my @build_subs; - my $linearized_isa = mro::get_linear_isa($class); - - for my $isa_class ( @$linearized_isa ) { - unshift @build_subs, *{ Mo::_glob_for "${isa_class}::BUILD" }{CODE}; - } - exists &$_ && $_->( $self, @_ ) for grep { defined } @build_subs; - return $self; - } - - sub BUILDARGS { - shift; - my $ref; - if ( @_ == 1 && ref($_[0]) ) { - Carp::confess("Single parameters to new() must be a HASH ref") - unless ref($_[0]) eq ref({}); - $ref = {%{$_[0]}} # We want a new reference, always - } - else { - $ref = { @_ }; - } - return $ref; - } -} - -my %export_for; -sub Mo::import { - warnings->import(qw(FATAL all)); - strict->import(); - - my $caller = scalar caller(); # Caller's package - my $caller_pkg = $caller . "::"; # Caller's package with :: at the end - my (%exports, %options); - - my (undef, @features) = @_; - my %ignore = ( map { $_ => 1 } qw( is isa init_arg builder buildargs clearer predicate build handles default required ) ); - for my $feature (grep { !$ignore{$_} } @features) { - { local $@; require "Mo/$feature.pm"; } - { - no strict 'refs'; - &{"Mo::${feature}::e"}( - $caller_pkg, - \%exports, - \%options, - \@_ - ); - } - } - - return if $exports{M}; - - %exports = ( - extends => sub { - for my $class ( map { "$_" } @_ ) { - $class =~ s{::|'}{/}g; - { local $@; eval { require "$class.pm" } } # or warn $@; - } - _set_package_isa($caller, @_); - _set_inherited_metadata($caller); - }, - has => sub { - my $names = shift; - for my $attribute ( ref $names ? @$names : $names ) { - my %args = @_; - my $method = ($args{is} || '') eq 'ro' - ? sub { - Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller_pkg}${attribute}") - if $#_; - return $_[0]{$attribute}; - } - : sub { - return $#_ - ? $_[0]{$attribute} = $_[1] - : $_[0]{$attribute}; - }; - - $metadata_for{$caller}{$attribute} = (); - - if ( my $I = $args{isa} ) { - my $orig_I = $I; - my $type; - if ( $I =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { - $I = _nested_constraints($attribute, $1, $2); - } - $metadata_for{$caller}{$attribute}{isa} = [$orig_I, $I]; - my $orig_method = $method; - $method = sub { - if ( $#_ ) { - Mo::_check_type_constaints($attribute, $I, $orig_I, $_[1]); - } - goto &$orig_method; - }; - } - - if ( my $builder = $args{builder} ) { - my $original_method = $method; - $method = sub { - $#_ - ? goto &$original_method - : ! exists $_[0]{$attribute} - ? $_[0]{$attribute} = $_[0]->$builder - : goto &$original_method - }; - } - - if ( my $code = $args{default} ) { - Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") - unless ref($code) eq 'CODE'; - my $original_method = $method; - $method = sub { - $#_ - ? goto &$original_method - : ! exists $_[0]{$attribute} - ? $_[0]{$attribute} = $_[0]->$code - : goto &$original_method - }; - } - - if ( my $role = $args{does} ) { - my $original_method = $method; - $method = sub { - if ( $#_ ) { - Carp::confess(qq) - unless blessed($_[1]) && $_[1]->does($role) - } - goto &$original_method - }; - } - - if ( my $coercion = $args{coerce} ) { - $metadata_for{$caller}{$attribute}{coerce} = $coercion; - my $original_method = $method; - $method = sub { - if ( $#_ ) { - return $original_method->($_[0], $coercion->($_[1])) - } - goto &$original_method; - } - } - - $method = $options{$_}->($method, $attribute, @_) - for sort keys %options; - - *{ _glob_for "${caller}::$attribute" } = $method; - - if ( $args{required} ) { - $metadata_for{$caller}{$attribute}{required} = 1; - } - - if ($args{clearer}) { - *{ _glob_for "${caller}::$args{clearer}" } - = sub { delete shift->{$attribute} } - } - - if ($args{predicate}) { - *{ _glob_for "${caller}::$args{predicate}" } - = sub { exists shift->{$attribute} } - } - - if ($args{handles}) { - _has_handles($caller, $attribute, \%args); - } - - if (exists $args{init_arg}) { - $metadata_for{$caller}{$attribute}{init_arg} = $args{init_arg}; - } - } - }, - %exports, - ); - - $export_for{$caller} = [ keys %exports ]; - - for my $keyword ( keys %exports ) { - *{ _glob_for "${caller}::$keyword" } = $exports{$keyword} - } - *{ _glob_for "${caller}::extends" }{CODE}->( "Mo::Object" ) - unless @{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] }; -}; - -sub _check_type_constaints { - my ($attribute, $I, $I_name, $val) = @_; - ( ref($I) eq 'CODE' - ? $I->($val) - : (ref $val eq $I - || ($val && $val eq $I) - || (exists $TYPES{$I} && $TYPES{$I}->($val))) - ) - || Carp::confess( - qq - . qq - . (defined $val ? Mo::Dumper($val) : 'undef') ) -} - -sub _has_handles { - my ($caller, $attribute, $args) = @_; - my $handles = $args->{handles}; - - my $ref = ref $handles; - my $kv; - if ( $ref eq ref [] ) { - $kv = { map { $_,$_ } @{$handles} }; - } - elsif ( $ref eq ref {} ) { - $kv = $handles; - } - elsif ( $ref eq ref qr// ) { - Carp::confess("Cannot delegate methods based on a Regexp without a type constraint (isa)") - unless $args->{isa}; - my $target_class = $args->{isa}; - $kv = { - map { $_, $_ } - grep { $_ =~ $handles } - grep { !exists $Mo::Object::{$_} && $target_class->can($_) } - grep { $_ ne 'has' && $_ ne 'extends' } - keys %{ _stash_for $target_class } - }; - } - else { - Carp::confess("handles for $ref not yet implemented"); - } - - while ( my ($method, $target) = each %{$kv} ) { - my $name = _glob_for "${caller}::$method"; - Carp::confess("You cannot overwrite a locally defined method ($method) with a delegation") - if defined &$name; - - my ($target, @curried_args) = ref($target) ? @$target : $target; - *$name = sub { - my $self = shift; - my $delegate_to = $self->$attribute(); - my $error = "Cannot delegate $method to $target because the value of $attribute"; - Carp::confess("$error is not defined") unless $delegate_to; - Carp::confess("$error is not an object (got '$delegate_to')") - unless Scalar::Util::blessed($delegate_to) || (!ref($delegate_to) && $delegate_to->can($target)); - return $delegate_to->$target(@curried_args, @_); - } - } -} - -sub _nested_constraints { - my ($attribute, $aggregate_type, $type) = @_; - - my $inner_types; - if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { - $inner_types = _nested_constraints($1, $2); - } - else { - $inner_types = $TYPES{$type}; - } - - if ( $aggregate_type eq 'ArrayRef' ) { - return sub { - my ($val) = @_; - return unless ref($val) eq ref([]); - - if ($inner_types) { - for my $value ( @{$val} ) { - return unless $inner_types->($value) - } - } - else { - for my $value ( @{$val} ) { - return unless $value && ($value eq $type - || (Scalar::Util::blessed($value) && $value->isa($type))); - } - } - return 1; - }; - } - elsif ( $aggregate_type eq 'Maybe' ) { - return sub { - my ($value) = @_; - return 1 if ! defined($value); - if ($inner_types) { - return unless $inner_types->($value) - } - else { - return unless $value eq $type - || (Scalar::Util::blessed($value) && $value->isa($type)); - } - return 1; - } - } - else { - Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); - } -} - -sub _set_package_isa { - my ($package, @new_isa) = @_; - - *{ _glob_for "${package}::ISA" } = [@new_isa]; -} - -sub _set_inherited_metadata { - my $class = shift; - my $linearized_isa = mro::get_linear_isa($class); - my %new_metadata; - - for my $isa_class (reverse @$linearized_isa) { - %new_metadata = ( - %new_metadata, - %{ $metadata_for{$isa_class} || {} }, - ); - } - $metadata_for{$class} = \%new_metadata; -} - -sub unimport { - my $caller = scalar caller(); - my $stash = _stash_for( $caller ); - - delete $stash->{$_} for @{$export_for{$caller}}; -} - -sub Dumper { - require Data::Dumper; - local $Data::Dumper::Indent = 0; - local $Data::Dumper::Sortkeys = 0; - local $Data::Dumper::Quotekeys = 0; - local $Data::Dumper::Terse = 1; - - Data::Dumper::Dumper(@_) -} - -BEGIN { - if ($] >= 5.010) { - { local $@; require mro; } - } - else { - local $@; - eval { - require MRO::Compat; - } or do { - *mro::get_linear_isa = *mro::get_linear_isa_dfs = sub { - no strict 'refs'; - - my $classname = shift; - - my @lin = ($classname); - my %stored; - foreach my $parent (@{"$classname\::ISA"}) { - my $plin = mro::get_linear_isa_dfs($parent); - foreach (@$plin) { - next if exists $stored{$_}; - push(@lin, $_); - $stored{$_} = 1; - } - } - return \@lin; - }; - } - } -} - -} -1; -} -# ########################################################################### -# End Mo package -# ########################################################################### - # ########################################################################### # Quoter package # This package is a copy without comments from the original. The original @@ -1963,200 +1513,6 @@ sub deserialize_list { # End Quoter package # ########################################################################### -# ########################################################################### -# VersionParser 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/VersionParser.pm -# t/lib/VersionParser.t -# See https://launchpad.net/percona-toolkit for more information. -# ########################################################################### -{ -package VersionParser; - -use Mo; -use Scalar::Util qw(blessed); -use English qw(-no_match_vars); -use constant PTDEBUG => $ENV{PTDEBUG} || 0; - -use overload ( - '""' => "version", - '<=>' => "cmp", - 'cmp' => "cmp", - fallback => 1, -); - -use Carp (); - -our $VERSION = 0.01; - -has major => ( - is => 'ro', - isa => 'Int', - required => 1, -); - -has [qw( minor revision )] => ( - is => 'ro', - isa => 'Num', -); - -has flavor => ( - is => 'ro', - isa => 'Str', - default => sub { 'Unknown' }, -); - -has innodb_version => ( - is => 'ro', - isa => 'Str', - default => sub { 'NO' }, -); - -sub series { - my $self = shift; - return $self->_join_version($self->major, $self->minor); -} - -sub version { - my $self = shift; - return $self->_join_version($self->major, $self->minor, $self->revision); -} - -sub is_in { - my ($self, $target) = @_; - - return $self eq $target; -} - -sub _join_version { - my ($self, @parts) = @_; - - return join ".", map { my $c = $_; $c =~ s/^0\./0/; $c } grep defined, @parts; -} -sub _split_version { - my ($self, $str) = @_; - my @version_parts = map { s/^0(?=\d)/0./; $_ } $str =~ m/(\d+)/g; - return @version_parts[0..2]; -} - -sub normalized_version { - my ( $self ) = @_; - my $result = sprintf('%d%02d%02d', map { $_ || 0 } $self->major, - $self->minor, - $self->revision); - PTDEBUG && _d($self->version, 'normalizes to', $result); - return $result; -} - -sub comment { - my ( $self, $cmd ) = @_; - my $v = $self->normalized_version(); - - return "/*!$v $cmd */" -} - -my @methods = qw(major minor revision); -sub cmp { - my ($left, $right) = @_; - my $right_obj = (blessed($right) && $right->isa(ref($left))) - ? $right - : ref($left)->new($right); - - my $retval = 0; - for my $m ( @methods ) { - last unless defined($left->$m) && defined($right_obj->$m); - $retval = $left->$m <=> $right_obj->$m; - last if $retval; - } - return $retval; -} - -sub BUILDARGS { - my $self = shift; - - if ( @_ == 1 ) { - my %args; - if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { - PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); - my $dbh = $_[0]; - local $dbh->{FetchHashKeyName} = 'NAME_lc'; - my $query = eval { - $dbh->selectall_arrayref(q, { Slice => {} }) - }; - if ( $query ) { - $query = { map { $_->{variable_name} => $_->{value} } @$query }; - @args{@methods} = $self->_split_version($query->{version}); - $args{flavor} = delete $query->{version_comment} - if $query->{version_comment}; - } - elsif ( eval { ($query) = $dbh->selectrow_array('SELECT VERSION()') } ) { - @args{@methods} = $self->_split_version($query); - } - else { - Carp::confess("Couldn't get the version from the dbh while " - . "creating a VersionParser object: $@"); - } - $args{innodb_version} = eval { $self->_innodb_version($dbh) }; - } - elsif ( !ref($_[0]) ) { - @args{@methods} = $self->_split_version($_[0]); - } - - for my $method (@methods) { - delete $args{$method} unless defined $args{$method}; - } - @_ = %args if %args; - } - - return $self->SUPER::BUILDARGS(@_); -} - -sub _innodb_version { - my ( $self, $dbh ) = @_; - return unless $dbh; - my $innodb_version = "NO"; - - my ($innodb) = - grep { $_->{engine} =~ m/InnoDB/i } - map { - my %hash; - @hash{ map { lc $_ } keys %$_ } = values %$_; - \%hash; - } - @{ $dbh->selectall_arrayref("SHOW ENGINES", {Slice=>{}}) }; - if ( $innodb ) { - PTDEBUG && _d("InnoDB support:", $innodb->{support}); - if ( $innodb->{support} =~ m/YES|DEFAULT/i ) { - my $vars = $dbh->selectrow_hashref( - "SHOW VARIABLES LIKE 'innodb_version'"); - $innodb_version = !$vars ? "BUILTIN" - : ($vars->{Value} || $vars->{value}); - } - else { - $innodb_version = $innodb->{support}; # probably DISABLED or NO - } - } - - PTDEBUG && _d("InnoDB version:", $innodb_version); - return $innodb_version; -} - -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"; -} - -no Mo; -1; -} -# ########################################################################### -# End VersionParser package -# ########################################################################### - # ########################################################################### # TableParser package # This package is a copy without comments from the original. The original @@ -2461,12 +1817,6 @@ sub get_keys { my ( $type, $cols ) = $key =~ m/(?:USING (\w+))? \((.+)\)/; my ( $special ) = $key =~ m/(FULLTEXT|SPATIAL)/; $type = $type || $special || 'BTREE'; - if ( $opts->{mysql_version} && $opts->{mysql_version} lt '4.1' - && $engine =~ m/HEAP|MEMORY/i ) - { - $type = 'HASH'; # MySQL pre-4.1 supports only HASH indexes on HEAP - } - my ($name) = $key =~ m/(PRIMARY|`[^`]*`)/; my $unique = $key =~ m/PRIMARY|UNIQUE/ ? 1 : 0; my @cols; @@ -3189,10 +2539,7 @@ sub main { ($server_id) = $dbh->selectrow_array('SELECT @@SERVER_ID'); # Discover if we need to get stored code. Need dbh to do this. - my $need_stored_code = VersionParser->new($dbh) >= '5.0.0'; - $need_stored_code = grep { $o->got($_); } @stored_code_tests - if $need_stored_code; - PTDEBUG && _d('Need stored code:', $need_stored_code); + my $need_stored_code = grep { $o->got($_); } @stored_code_tests; # ######################################################################## # Go do it. diff --git a/bin/pt-heartbeat b/bin/pt-heartbeat index 442b326b..df0fc5c2 100755 --- a/bin/pt-heartbeat +++ b/bin/pt-heartbeat @@ -2710,12 +2710,6 @@ sub get_keys { my ( $type, $cols ) = $key =~ m/(?:USING (\w+))? \((.+)\)/; my ( $special ) = $key =~ m/(FULLTEXT|SPATIAL)/; $type = $type || $special || 'BTREE'; - if ( $opts->{mysql_version} && $opts->{mysql_version} lt '4.1' - && $engine =~ m/HEAP|MEMORY/i ) - { - $type = 'HASH'; # MySQL pre-4.1 supports only HASH indexes on HEAP - } - my ($name) = $key =~ m/(PRIMARY|`[^`]*`)/; my $unique = $key =~ m/PRIMARY|UNIQUE/ ? 1 : 0; my @cols; diff --git a/bin/pt-index-usage b/bin/pt-index-usage index 625f831d..f280658c 100755 --- a/bin/pt-index-usage +++ b/bin/pt-index-usage @@ -1513,456 +1513,6 @@ if ( PTDEBUG ) { # End OptionParser package # ########################################################################### -# ########################################################################### -# Mo 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/Mo.pm -# t/lib/Mo.t -# See https://launchpad.net/percona-toolkit for more information. -# ########################################################################### -{ -BEGIN { -$INC{"Mo.pm"} = __FILE__; -package Mo; -our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. - -{ - no strict 'refs'; - sub _glob_for { - return \*{shift()} - } - - sub _stash_for { - return \%{ shift() . "::" }; - } -} - -use strict; -use warnings qw( FATAL all ); - -use Carp (); -use Scalar::Util (); - -our %TYPES = ( - Bool => sub { !$_[0] || (defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == 1) }, - Num => sub { defined $_[0] && &Scalar::Util::looks_like_number }, - Int => sub { defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == int $_[0] }, - Str => sub { defined $_[0] }, - Object => sub { defined $_[0] && &Scalar::Util::blessed }, - FileHandle => sub { local $@; require IO::Handle; fileno($_[0]) && $_[0]->opened }, - - map { - my $type = /R/ ? $_ : uc $_; - $_ . "Ref" => sub { ref $_[0] eq $type } - } qw(Array Code Hash Regexp Glob Scalar) -); - -our %metadata_for; -{ - package Mo::Object; - - sub new { - my $class = shift; - my $args = $class->BUILDARGS(@_); - - my @args_to_delete; - while ( my ($attr, $meta) = each %{$metadata_for{$class}} ) { - next unless exists $meta->{init_arg}; - my $init_arg = $meta->{init_arg}; - - if ( defined $init_arg ) { - $args->{$attr} = delete $args->{$init_arg}; - } - else { - push @args_to_delete, $attr; - } - } - - delete $args->{$_} for @args_to_delete; - - for my $attribute ( keys %$args ) { - if ( my $coerce = $metadata_for{$class}{$attribute}{coerce} ) { - $args->{$attribute} = $coerce->($args->{$attribute}); - } - if ( my $I = $metadata_for{$class}{$attribute}{isa} ) { - ( (my $I_name), $I ) = @{$I}; - Mo::_check_type_constaints($attribute, $I, $I_name, $args->{$attribute}); - } - } - - while ( my ($attribute, $meta) = each %{$metadata_for{$class}} ) { - next unless $meta->{required}; - Carp::confess("Attribute ($attribute) is required for $class") - if ! exists $args->{$attribute} - } - - @_ = %$args; - my $self = bless $args, $class; - - my @build_subs; - my $linearized_isa = mro::get_linear_isa($class); - - for my $isa_class ( @$linearized_isa ) { - unshift @build_subs, *{ Mo::_glob_for "${isa_class}::BUILD" }{CODE}; - } - exists &$_ && $_->( $self, @_ ) for grep { defined } @build_subs; - return $self; - } - - sub BUILDARGS { - shift; - my $ref; - if ( @_ == 1 && ref($_[0]) ) { - Carp::confess("Single parameters to new() must be a HASH ref") - unless ref($_[0]) eq ref({}); - $ref = {%{$_[0]}} # We want a new reference, always - } - else { - $ref = { @_ }; - } - return $ref; - } -} - -my %export_for; -sub Mo::import { - warnings->import(qw(FATAL all)); - strict->import(); - - my $caller = scalar caller(); # Caller's package - my $caller_pkg = $caller . "::"; # Caller's package with :: at the end - my (%exports, %options); - - my (undef, @features) = @_; - my %ignore = ( map { $_ => 1 } qw( is isa init_arg builder buildargs clearer predicate build handles default required ) ); - for my $feature (grep { !$ignore{$_} } @features) { - { local $@; require "Mo/$feature.pm"; } - { - no strict 'refs'; - &{"Mo::${feature}::e"}( - $caller_pkg, - \%exports, - \%options, - \@_ - ); - } - } - - return if $exports{M}; - - %exports = ( - extends => sub { - for my $class ( map { "$_" } @_ ) { - $class =~ s{::|'}{/}g; - { local $@; eval { require "$class.pm" } } # or warn $@; - } - _set_package_isa($caller, @_); - _set_inherited_metadata($caller); - }, - has => sub { - my $names = shift; - for my $attribute ( ref $names ? @$names : $names ) { - my %args = @_; - my $method = ($args{is} || '') eq 'ro' - ? sub { - Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller_pkg}${attribute}") - if $#_; - return $_[0]{$attribute}; - } - : sub { - return $#_ - ? $_[0]{$attribute} = $_[1] - : $_[0]{$attribute}; - }; - - $metadata_for{$caller}{$attribute} = (); - - if ( my $I = $args{isa} ) { - my $orig_I = $I; - my $type; - if ( $I =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { - $I = _nested_constraints($attribute, $1, $2); - } - $metadata_for{$caller}{$attribute}{isa} = [$orig_I, $I]; - my $orig_method = $method; - $method = sub { - if ( $#_ ) { - Mo::_check_type_constaints($attribute, $I, $orig_I, $_[1]); - } - goto &$orig_method; - }; - } - - if ( my $builder = $args{builder} ) { - my $original_method = $method; - $method = sub { - $#_ - ? goto &$original_method - : ! exists $_[0]{$attribute} - ? $_[0]{$attribute} = $_[0]->$builder - : goto &$original_method - }; - } - - if ( my $code = $args{default} ) { - Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") - unless ref($code) eq 'CODE'; - my $original_method = $method; - $method = sub { - $#_ - ? goto &$original_method - : ! exists $_[0]{$attribute} - ? $_[0]{$attribute} = $_[0]->$code - : goto &$original_method - }; - } - - if ( my $role = $args{does} ) { - my $original_method = $method; - $method = sub { - if ( $#_ ) { - Carp::confess(qq) - unless blessed($_[1]) && $_[1]->does($role) - } - goto &$original_method - }; - } - - if ( my $coercion = $args{coerce} ) { - $metadata_for{$caller}{$attribute}{coerce} = $coercion; - my $original_method = $method; - $method = sub { - if ( $#_ ) { - return $original_method->($_[0], $coercion->($_[1])) - } - goto &$original_method; - } - } - - $method = $options{$_}->($method, $attribute, @_) - for sort keys %options; - - *{ _glob_for "${caller}::$attribute" } = $method; - - if ( $args{required} ) { - $metadata_for{$caller}{$attribute}{required} = 1; - } - - if ($args{clearer}) { - *{ _glob_for "${caller}::$args{clearer}" } - = sub { delete shift->{$attribute} } - } - - if ($args{predicate}) { - *{ _glob_for "${caller}::$args{predicate}" } - = sub { exists shift->{$attribute} } - } - - if ($args{handles}) { - _has_handles($caller, $attribute, \%args); - } - - if (exists $args{init_arg}) { - $metadata_for{$caller}{$attribute}{init_arg} = $args{init_arg}; - } - } - }, - %exports, - ); - - $export_for{$caller} = [ keys %exports ]; - - for my $keyword ( keys %exports ) { - *{ _glob_for "${caller}::$keyword" } = $exports{$keyword} - } - *{ _glob_for "${caller}::extends" }{CODE}->( "Mo::Object" ) - unless @{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] }; -}; - -sub _check_type_constaints { - my ($attribute, $I, $I_name, $val) = @_; - ( ref($I) eq 'CODE' - ? $I->($val) - : (ref $val eq $I - || ($val && $val eq $I) - || (exists $TYPES{$I} && $TYPES{$I}->($val))) - ) - || Carp::confess( - qq - . qq - . (defined $val ? Mo::Dumper($val) : 'undef') ) -} - -sub _has_handles { - my ($caller, $attribute, $args) = @_; - my $handles = $args->{handles}; - - my $ref = ref $handles; - my $kv; - if ( $ref eq ref [] ) { - $kv = { map { $_,$_ } @{$handles} }; - } - elsif ( $ref eq ref {} ) { - $kv = $handles; - } - elsif ( $ref eq ref qr// ) { - Carp::confess("Cannot delegate methods based on a Regexp without a type constraint (isa)") - unless $args->{isa}; - my $target_class = $args->{isa}; - $kv = { - map { $_, $_ } - grep { $_ =~ $handles } - grep { !exists $Mo::Object::{$_} && $target_class->can($_) } - grep { $_ ne 'has' && $_ ne 'extends' } - keys %{ _stash_for $target_class } - }; - } - else { - Carp::confess("handles for $ref not yet implemented"); - } - - while ( my ($method, $target) = each %{$kv} ) { - my $name = _glob_for "${caller}::$method"; - Carp::confess("You cannot overwrite a locally defined method ($method) with a delegation") - if defined &$name; - - my ($target, @curried_args) = ref($target) ? @$target : $target; - *$name = sub { - my $self = shift; - my $delegate_to = $self->$attribute(); - my $error = "Cannot delegate $method to $target because the value of $attribute"; - Carp::confess("$error is not defined") unless $delegate_to; - Carp::confess("$error is not an object (got '$delegate_to')") - unless Scalar::Util::blessed($delegate_to) || (!ref($delegate_to) && $delegate_to->can($target)); - return $delegate_to->$target(@curried_args, @_); - } - } -} - -sub _nested_constraints { - my ($attribute, $aggregate_type, $type) = @_; - - my $inner_types; - if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { - $inner_types = _nested_constraints($1, $2); - } - else { - $inner_types = $TYPES{$type}; - } - - if ( $aggregate_type eq 'ArrayRef' ) { - return sub { - my ($val) = @_; - return unless ref($val) eq ref([]); - - if ($inner_types) { - for my $value ( @{$val} ) { - return unless $inner_types->($value) - } - } - else { - for my $value ( @{$val} ) { - return unless $value && ($value eq $type - || (Scalar::Util::blessed($value) && $value->isa($type))); - } - } - return 1; - }; - } - elsif ( $aggregate_type eq 'Maybe' ) { - return sub { - my ($value) = @_; - return 1 if ! defined($value); - if ($inner_types) { - return unless $inner_types->($value) - } - else { - return unless $value eq $type - || (Scalar::Util::blessed($value) && $value->isa($type)); - } - return 1; - } - } - else { - Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); - } -} - -sub _set_package_isa { - my ($package, @new_isa) = @_; - - *{ _glob_for "${package}::ISA" } = [@new_isa]; -} - -sub _set_inherited_metadata { - my $class = shift; - my $linearized_isa = mro::get_linear_isa($class); - my %new_metadata; - - for my $isa_class (reverse @$linearized_isa) { - %new_metadata = ( - %new_metadata, - %{ $metadata_for{$isa_class} || {} }, - ); - } - $metadata_for{$class} = \%new_metadata; -} - -sub unimport { - my $caller = scalar caller(); - my $stash = _stash_for( $caller ); - - delete $stash->{$_} for @{$export_for{$caller}}; -} - -sub Dumper { - require Data::Dumper; - local $Data::Dumper::Indent = 0; - local $Data::Dumper::Sortkeys = 0; - local $Data::Dumper::Quotekeys = 0; - local $Data::Dumper::Terse = 1; - - Data::Dumper::Dumper(@_) -} - -BEGIN { - if ($] >= 5.010) { - { local $@; require mro; } - } - else { - local $@; - eval { - require MRO::Compat; - } or do { - *mro::get_linear_isa = *mro::get_linear_isa_dfs = sub { - no strict 'refs'; - - my $classname = shift; - - my @lin = ($classname); - my %stored; - foreach my $parent (@{"$classname\::ISA"}) { - my $plin = mro::get_linear_isa_dfs($parent); - foreach (@$plin) { - next if exists $stored{$_}; - push(@lin, $_); - $stored{$_} = 1; - } - } - return \@lin; - }; - } - } -} - -} -1; -} -# ########################################################################### -# End Mo package -# ########################################################################### - # ########################################################################### # PodParser package # This package is a copy without comments from the original. The original @@ -3456,12 +3006,6 @@ sub get_keys { my ( $type, $cols ) = $key =~ m/(?:USING (\w+))? \((.+)\)/; my ( $special ) = $key =~ m/(FULLTEXT|SPATIAL)/; $type = $type || $special || 'BTREE'; - if ( $opts->{mysql_version} && $opts->{mysql_version} lt '4.1' - && $engine =~ m/HEAP|MEMORY/i ) - { - $type = 'HASH'; # MySQL pre-4.1 supports only HASH indexes on HEAP - } - my ($name) = $key =~ m/(PRIMARY|`[^`]*`)/; my $unique = $key =~ m/PRIMARY|UNIQUE/ ? 1 : 0; my @cols; @@ -3947,200 +3491,6 @@ sub _d { # End Transformers package # ########################################################################### -# ########################################################################### -# VersionParser 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/VersionParser.pm -# t/lib/VersionParser.t -# See https://launchpad.net/percona-toolkit for more information. -# ########################################################################### -{ -package VersionParser; - -use Mo; -use Scalar::Util qw(blessed); -use English qw(-no_match_vars); -use constant PTDEBUG => $ENV{PTDEBUG} || 0; - -use overload ( - '""' => "version", - '<=>' => "cmp", - 'cmp' => "cmp", - fallback => 1, -); - -use Carp (); - -our $VERSION = 0.01; - -has major => ( - is => 'ro', - isa => 'Int', - required => 1, -); - -has [qw( minor revision )] => ( - is => 'ro', - isa => 'Num', -); - -has flavor => ( - is => 'ro', - isa => 'Str', - default => sub { 'Unknown' }, -); - -has innodb_version => ( - is => 'ro', - isa => 'Str', - default => sub { 'NO' }, -); - -sub series { - my $self = shift; - return $self->_join_version($self->major, $self->minor); -} - -sub version { - my $self = shift; - return $self->_join_version($self->major, $self->minor, $self->revision); -} - -sub is_in { - my ($self, $target) = @_; - - return $self eq $target; -} - -sub _join_version { - my ($self, @parts) = @_; - - return join ".", map { my $c = $_; $c =~ s/^0\./0/; $c } grep defined, @parts; -} -sub _split_version { - my ($self, $str) = @_; - my @version_parts = map { s/^0(?=\d)/0./; $_ } $str =~ m/(\d+)/g; - return @version_parts[0..2]; -} - -sub normalized_version { - my ( $self ) = @_; - my $result = sprintf('%d%02d%02d', map { $_ || 0 } $self->major, - $self->minor, - $self->revision); - PTDEBUG && _d($self->version, 'normalizes to', $result); - return $result; -} - -sub comment { - my ( $self, $cmd ) = @_; - my $v = $self->normalized_version(); - - return "/*!$v $cmd */" -} - -my @methods = qw(major minor revision); -sub cmp { - my ($left, $right) = @_; - my $right_obj = (blessed($right) && $right->isa(ref($left))) - ? $right - : ref($left)->new($right); - - my $retval = 0; - for my $m ( @methods ) { - last unless defined($left->$m) && defined($right_obj->$m); - $retval = $left->$m <=> $right_obj->$m; - last if $retval; - } - return $retval; -} - -sub BUILDARGS { - my $self = shift; - - if ( @_ == 1 ) { - my %args; - if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { - PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); - my $dbh = $_[0]; - local $dbh->{FetchHashKeyName} = 'NAME_lc'; - my $query = eval { - $dbh->selectall_arrayref(q, { Slice => {} }) - }; - if ( $query ) { - $query = { map { $_->{variable_name} => $_->{value} } @$query }; - @args{@methods} = $self->_split_version($query->{version}); - $args{flavor} = delete $query->{version_comment} - if $query->{version_comment}; - } - elsif ( eval { ($query) = $dbh->selectrow_array('SELECT VERSION()') } ) { - @args{@methods} = $self->_split_version($query); - } - else { - Carp::confess("Couldn't get the version from the dbh while " - . "creating a VersionParser object: $@"); - } - $args{innodb_version} = eval { $self->_innodb_version($dbh) }; - } - elsif ( !ref($_[0]) ) { - @args{@methods} = $self->_split_version($_[0]); - } - - for my $method (@methods) { - delete $args{$method} unless defined $args{$method}; - } - @_ = %args if %args; - } - - return $self->SUPER::BUILDARGS(@_); -} - -sub _innodb_version { - my ( $self, $dbh ) = @_; - return unless $dbh; - my $innodb_version = "NO"; - - my ($innodb) = - grep { $_->{engine} =~ m/InnoDB/i } - map { - my %hash; - @hash{ map { lc $_ } keys %$_ } = values %$_; - \%hash; - } - @{ $dbh->selectall_arrayref("SHOW ENGINES", {Slice=>{}}) }; - if ( $innodb ) { - PTDEBUG && _d("InnoDB support:", $innodb->{support}); - if ( $innodb->{support} =~ m/YES|DEFAULT/i ) { - my $vars = $dbh->selectrow_hashref( - "SHOW VARIABLES LIKE 'innodb_version'"); - $innodb_version = !$vars ? "BUILTIN" - : ($vars->{Value} || $vars->{value}); - } - else { - $innodb_version = $innodb->{support}; # probably DISABLED or NO - } - } - - PTDEBUG && _d("InnoDB version:", $innodb_version); - return $innodb_version; -} - -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"; -} - -no Mo; -1; -} -# ########################################################################### -# End VersionParser package -# ########################################################################### - # ########################################################################### # Schema package # This package is a copy without comments from the original. The original @@ -5650,7 +5000,6 @@ sub main { # guess which database to USE for EXPLAIN-ing it. This code block doesn't # read query logs, it's just inventorying the tables and indexes. # ######################################################################## - my $version = VersionParser->new($dbh); my $schema = new Schema(); my $schema_itr = new SchemaIterator( @@ -5667,7 +5016,7 @@ sub main { if ( $ddl =~ m/CREATE (?:TEMPORARY )?TABLE "/ ) { $ddl = $tp->ansi_to_legacy($ddl); } - my ($indexes) = $tp->get_keys($ddl, {mysql_version => $version}); + my ($indexes) = $tp->get_keys($ddl, {}); $iu->add_indexes(%$tbl, indexes=>$indexes); }; if ( $EVAL_ERROR ) { diff --git a/bin/pt-online-schema-change b/bin/pt-online-schema-change index 2c25373f..26bf366f 100755 --- a/bin/pt-online-schema-change +++ b/bin/pt-online-schema-change @@ -1602,7 +1602,7 @@ sub BUILDARGS { my $dbh = $_[0]; local $dbh->{FetchHashKeyName} = 'NAME_lc'; my $query = eval { - $dbh->selectall_arrayref(q, { Slice => {} }) + $dbh->selectall_arrayref(q/SHOW VARIABLES LIKE 'version%'/, { Slice => {} }) }; if ( $query ) { $query = { map { $_->{variable_name} => $_->{value} } @$query }; @@ -1610,7 +1610,7 @@ sub BUILDARGS { $args{flavor} = delete $query->{version_comment} if $query->{version_comment}; } - elsif ( eval { ($query) = $dbh->selectrow_array('SELECT VERSION()') } ) { + elsif ( eval { ($query) = $dbh->selectrow_array(q/SELECT VERSION()/) } ) { @args{@methods} = $self->_split_version($query); } else { @@ -2913,12 +2913,6 @@ sub get_keys { my ( $type, $cols ) = $key =~ m/(?:USING (\w+))? \((.+)\)/; my ( $special ) = $key =~ m/(FULLTEXT|SPATIAL)/; $type = $type || $special || 'BTREE'; - if ( $opts->{mysql_version} && $opts->{mysql_version} lt '4.1' - && $engine =~ m/HEAP|MEMORY/i ) - { - $type = 'HASH'; # MySQL pre-4.1 supports only HASH indexes on HEAP - } - my ($name) = $key =~ m/(PRIMARY|`[^`]*`)/; my $unique = $key =~ m/PRIMARY|UNIQUE/ ? 1 : 0; my @cols; diff --git a/bin/pt-query-advisor b/bin/pt-query-advisor index 721606f9..34674492 100755 --- a/bin/pt-query-advisor +++ b/bin/pt-query-advisor @@ -5495,12 +5495,6 @@ sub get_keys { my ( $type, $cols ) = $key =~ m/(?:USING (\w+))? \((.+)\)/; my ( $special ) = $key =~ m/(FULLTEXT|SPATIAL)/; $type = $type || $special || 'BTREE'; - if ( $opts->{mysql_version} && $opts->{mysql_version} lt '4.1' - && $engine =~ m/HEAP|MEMORY/i ) - { - $type = 'HASH'; # MySQL pre-4.1 supports only HASH indexes on HEAP - } - my ($name) = $key =~ m/(PRIMARY|`[^`]*`)/; my $unique = $key =~ m/PRIMARY|UNIQUE/ ? 1 : 0; my @cols; diff --git a/bin/pt-query-digest b/bin/pt-query-digest index 688c8ecf..c115fba4 100755 --- a/bin/pt-query-digest +++ b/bin/pt-query-digest @@ -8296,12 +8296,6 @@ sub get_keys { my ( $type, $cols ) = $key =~ m/(?:USING (\w+))? \((.+)\)/; my ( $special ) = $key =~ m/(FULLTEXT|SPATIAL)/; $type = $type || $special || 'BTREE'; - if ( $opts->{mysql_version} && $opts->{mysql_version} lt '4.1' - && $engine =~ m/HEAP|MEMORY/i ) - { - $type = 'HASH'; # MySQL pre-4.1 supports only HASH indexes on HEAP - } - my ($name) = $key =~ m/(PRIMARY|`[^`]*`)/; my $unique = $key =~ m/PRIMARY|UNIQUE/ ? 1 : 0; my @cols; diff --git a/bin/pt-slave-find b/bin/pt-slave-find index f55a9a67..7df07085 100755 --- a/bin/pt-slave-find +++ b/bin/pt-slave-find @@ -2856,7 +2856,7 @@ sub BUILDARGS { my $dbh = $_[0]; local $dbh->{FetchHashKeyName} = 'NAME_lc'; my $query = eval { - $dbh->selectall_arrayref(q, { Slice => {} }) + $dbh->selectall_arrayref(q/SHOW VARIABLES LIKE 'version%'/, { Slice => {} }) }; if ( $query ) { $query = { map { $_->{variable_name} => $_->{value} } @$query }; @@ -2864,7 +2864,7 @@ sub BUILDARGS { $args{flavor} = delete $query->{version_comment} if $query->{version_comment}; } - elsif ( eval { ($query) = $dbh->selectrow_array('SELECT VERSION()') } ) { + elsif ( eval { ($query) = $dbh->selectrow_array(q/SELECT VERSION()/) } ) { @args{@methods} = $self->_split_version($query); } else { diff --git a/bin/pt-slave-restart b/bin/pt-slave-restart index 90ae54ef..d630fd49 100755 --- a/bin/pt-slave-restart +++ b/bin/pt-slave-restart @@ -1721,7 +1721,7 @@ sub BUILDARGS { my $dbh = $_[0]; local $dbh->{FetchHashKeyName} = 'NAME_lc'; my $query = eval { - $dbh->selectall_arrayref(q, { Slice => {} }) + $dbh->selectall_arrayref(q/SHOW VARIABLES LIKE 'version%'/, { Slice => {} }) }; if ( $query ) { $query = { map { $_->{variable_name} => $_->{value} } @$query }; @@ -1729,7 +1729,7 @@ sub BUILDARGS { $args{flavor} = delete $query->{version_comment} if $query->{version_comment}; } - elsif ( eval { ($query) = $dbh->selectrow_array('SELECT VERSION()') } ) { + elsif ( eval { ($query) = $dbh->selectrow_array(q/SELECT VERSION()/) } ) { @args{@methods} = $self->_split_version($query); } else { diff --git a/bin/pt-table-checksum b/bin/pt-table-checksum index c44c7ffc..345a24a9 100755 --- a/bin/pt-table-checksum +++ b/bin/pt-table-checksum @@ -2226,7 +2226,7 @@ sub BUILDARGS { my $dbh = $_[0]; local $dbh->{FetchHashKeyName} = 'NAME_lc'; my $query = eval { - $dbh->selectall_arrayref(q, { Slice => {} }) + $dbh->selectall_arrayref(q/SHOW VARIABLES LIKE 'version%'/, { Slice => {} }) }; if ( $query ) { $query = { map { $_->{variable_name} => $_->{value} } @$query }; @@ -2234,7 +2234,7 @@ sub BUILDARGS { $args{flavor} = delete $query->{version_comment} if $query->{version_comment}; } - elsif ( eval { ($query) = $dbh->selectrow_array('SELECT VERSION()') } ) { + elsif ( eval { ($query) = $dbh->selectrow_array(q/SELECT VERSION()/) } ) { @args{@methods} = $self->_split_version($query); } else { @@ -2605,12 +2605,6 @@ sub get_keys { my ( $type, $cols ) = $key =~ m/(?:USING (\w+))? \((.+)\)/; my ( $special ) = $key =~ m/(FULLTEXT|SPATIAL)/; $type = $type || $special || 'BTREE'; - if ( $opts->{mysql_version} && $opts->{mysql_version} lt '4.1' - && $engine =~ m/HEAP|MEMORY/i ) - { - $type = 'HASH'; # MySQL pre-4.1 supports only HASH indexes on HEAP - } - my ($name) = $key =~ m/(PRIMARY|`[^`]*`)/; my $unique = $key =~ m/PRIMARY|UNIQUE/ ? 1 : 0; my @cols; diff --git a/bin/pt-table-sync b/bin/pt-table-sync index c97437a5..08911dbb 100755 --- a/bin/pt-table-sync +++ b/bin/pt-table-sync @@ -2082,7 +2082,7 @@ sub BUILDARGS { my $dbh = $_[0]; local $dbh->{FetchHashKeyName} = 'NAME_lc'; my $query = eval { - $dbh->selectall_arrayref(q, { Slice => {} }) + $dbh->selectall_arrayref(q/SHOW VARIABLES LIKE 'version%'/, { Slice => {} }) }; if ( $query ) { $query = { map { $_->{variable_name} => $_->{value} } @$query }; @@ -2090,7 +2090,7 @@ sub BUILDARGS { $args{flavor} = delete $query->{version_comment} if $query->{version_comment}; } - elsif ( eval { ($query) = $dbh->selectrow_array('SELECT VERSION()') } ) { + elsif ( eval { ($query) = $dbh->selectrow_array(q/SELECT VERSION()/) } ) { @args{@methods} = $self->_split_version($query); } else { @@ -2580,12 +2580,6 @@ sub get_keys { my ( $type, $cols ) = $key =~ m/(?:USING (\w+))? \((.+)\)/; my ( $special ) = $key =~ m/(FULLTEXT|SPATIAL)/; $type = $type || $special || 'BTREE'; - if ( $opts->{mysql_version} && $opts->{mysql_version} lt '4.1' - && $engine =~ m/HEAP|MEMORY/i ) - { - $type = 'HASH'; # MySQL pre-4.1 supports only HASH indexes on HEAP - } - my ($name) = $key =~ m/(PRIMARY|`[^`]*`)/; my $unique = $key =~ m/PRIMARY|UNIQUE/ ? 1 : 0; my @cols; @@ -4554,21 +4548,14 @@ sub best_algorithm { die "Invalid checksum algorithm $alg" if $alg && !$ALGOS{$alg}; - my $version = VersionParser->new($dbh); - if ( $args{where} || $args{chunk} # CHECKSUM does whole table - || $args{replicate} # CHECKSUM can't do INSERT.. SELECT - || $version < '4.1.1') # CHECKSUM doesn't exist + || $args{replicate}) # CHECKSUM can't do INSERT.. SELECT { PTDEBUG && _d('Cannot use CHECKSUM algorithm'); @choices = grep { $_ ne 'CHECKSUM' } @choices; } - if ( $version < '4.1.1' ) { - PTDEBUG && _d('Cannot use BIT_XOR algorithm because MySQL < 4.1.1'); - @choices = grep { $_ ne 'BIT_XOR' } @choices; - } if ( $alg && grep { $_ eq $alg } @choices ) { PTDEBUG && _d('User requested', $alg, 'algorithm'); @@ -5756,16 +5743,13 @@ sub sync_table { PTDEBUG && _d('CRC column:', $crc_col); my $index_hint; - my $hint = ((VersionParser->new($src->{dbh}) >= '4.0.9' - && VersionParser->new($dst->{dbh}) >= '4.0.9') ? 'FORCE' : 'USE') - . ' INDEX'; if ( $args{chunk_index} ) { PTDEBUG && _d('Using given chunk index for index hint'); - $index_hint = "$hint (" . $q->quote($args{chunk_index}) . ")"; + $index_hint = "FORCE INDEX (" . $q->quote($args{chunk_index}) . ")"; } elsif ( $plugin_args{chunk_index} && $args{index_hint} ) { PTDEBUG && _d('Using chunk index chosen by plugin for index hint'); - $index_hint = "$hint (" . $q->quote($plugin_args{chunk_index}) . ")"; + $index_hint = "FORCE INDEX (" . $q->quote($plugin_args{chunk_index}) . ")"; } PTDEBUG && _d('Index hint:', $index_hint); diff --git a/bin/pt-table-usage b/bin/pt-table-usage index 5204edad..ae157624 100755 --- a/bin/pt-table-usage +++ b/bin/pt-table-usage @@ -5868,12 +5868,6 @@ sub get_keys { my ( $type, $cols ) = $key =~ m/(?:USING (\w+))? \((.+)\)/; my ( $special ) = $key =~ m/(FULLTEXT|SPATIAL)/; $type = $type || $special || 'BTREE'; - if ( $opts->{mysql_version} && $opts->{mysql_version} lt '4.1' - && $engine =~ m/HEAP|MEMORY/i ) - { - $type = 'HASH'; # MySQL pre-4.1 supports only HASH indexes on HEAP - } - my ($name) = $key =~ m/(PRIMARY|`[^`]*`)/; my $unique = $key =~ m/PRIMARY|UNIQUE/ ? 1 : 0; my @cols; diff --git a/bin/pt-upgrade b/bin/pt-upgrade index f8ee43d2..f39344a4 100755 --- a/bin/pt-upgrade +++ b/bin/pt-upgrade @@ -673,12 +673,6 @@ sub get_keys { my ( $type, $cols ) = $key =~ m/(?:USING (\w+))? \((.+)\)/; my ( $special ) = $key =~ m/(FULLTEXT|SPATIAL)/; $type = $type || $special || 'BTREE'; - if ( $opts->{mysql_version} && $opts->{mysql_version} lt '4.1' - && $engine =~ m/HEAP|MEMORY/i ) - { - $type = 'HASH'; # MySQL pre-4.1 supports only HASH indexes on HEAP - } - my ($name) = $key =~ m/(PRIMARY|`[^`]*`)/; my $unique = $key =~ m/PRIMARY|UNIQUE/ ? 1 : 0; my @cols; @@ -1960,456 +1954,6 @@ if ( PTDEBUG ) { # End OptionParser package # ########################################################################### -# ########################################################################### -# Mo 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/Mo.pm -# t/lib/Mo.t -# See https://launchpad.net/percona-toolkit for more information. -# ########################################################################### -{ -BEGIN { -$INC{"Mo.pm"} = __FILE__; -package Mo; -our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. - -{ - no strict 'refs'; - sub _glob_for { - return \*{shift()} - } - - sub _stash_for { - return \%{ shift() . "::" }; - } -} - -use strict; -use warnings qw( FATAL all ); - -use Carp (); -use Scalar::Util (); - -our %TYPES = ( - Bool => sub { !$_[0] || (defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == 1) }, - Num => sub { defined $_[0] && &Scalar::Util::looks_like_number }, - Int => sub { defined $_[0] && &Scalar::Util::looks_like_number && $_[0] == int $_[0] }, - Str => sub { defined $_[0] }, - Object => sub { defined $_[0] && &Scalar::Util::blessed }, - FileHandle => sub { local $@; require IO::Handle; fileno($_[0]) && $_[0]->opened }, - - map { - my $type = /R/ ? $_ : uc $_; - $_ . "Ref" => sub { ref $_[0] eq $type } - } qw(Array Code Hash Regexp Glob Scalar) -); - -our %metadata_for; -{ - package Mo::Object; - - sub new { - my $class = shift; - my $args = $class->BUILDARGS(@_); - - my @args_to_delete; - while ( my ($attr, $meta) = each %{$metadata_for{$class}} ) { - next unless exists $meta->{init_arg}; - my $init_arg = $meta->{init_arg}; - - if ( defined $init_arg ) { - $args->{$attr} = delete $args->{$init_arg}; - } - else { - push @args_to_delete, $attr; - } - } - - delete $args->{$_} for @args_to_delete; - - for my $attribute ( keys %$args ) { - if ( my $coerce = $metadata_for{$class}{$attribute}{coerce} ) { - $args->{$attribute} = $coerce->($args->{$attribute}); - } - if ( my $I = $metadata_for{$class}{$attribute}{isa} ) { - ( (my $I_name), $I ) = @{$I}; - Mo::_check_type_constaints($attribute, $I, $I_name, $args->{$attribute}); - } - } - - while ( my ($attribute, $meta) = each %{$metadata_for{$class}} ) { - next unless $meta->{required}; - Carp::confess("Attribute ($attribute) is required for $class") - if ! exists $args->{$attribute} - } - - @_ = %$args; - my $self = bless $args, $class; - - my @build_subs; - my $linearized_isa = mro::get_linear_isa($class); - - for my $isa_class ( @$linearized_isa ) { - unshift @build_subs, *{ Mo::_glob_for "${isa_class}::BUILD" }{CODE}; - } - exists &$_ && $_->( $self, @_ ) for grep { defined } @build_subs; - return $self; - } - - sub BUILDARGS { - shift; - my $ref; - if ( @_ == 1 && ref($_[0]) ) { - Carp::confess("Single parameters to new() must be a HASH ref") - unless ref($_[0]) eq ref({}); - $ref = {%{$_[0]}} # We want a new reference, always - } - else { - $ref = { @_ }; - } - return $ref; - } -} - -my %export_for; -sub Mo::import { - warnings->import(qw(FATAL all)); - strict->import(); - - my $caller = scalar caller(); # Caller's package - my $caller_pkg = $caller . "::"; # Caller's package with :: at the end - my (%exports, %options); - - my (undef, @features) = @_; - my %ignore = ( map { $_ => 1 } qw( is isa init_arg builder buildargs clearer predicate build handles default required ) ); - for my $feature (grep { !$ignore{$_} } @features) { - { local $@; require "Mo/$feature.pm"; } - { - no strict 'refs'; - &{"Mo::${feature}::e"}( - $caller_pkg, - \%exports, - \%options, - \@_ - ); - } - } - - return if $exports{M}; - - %exports = ( - extends => sub { - for my $class ( map { "$_" } @_ ) { - $class =~ s{::|'}{/}g; - { local $@; eval { require "$class.pm" } } # or warn $@; - } - _set_package_isa($caller, @_); - _set_inherited_metadata($caller); - }, - has => sub { - my $names = shift; - for my $attribute ( ref $names ? @$names : $names ) { - my %args = @_; - my $method = ($args{is} || '') eq 'ro' - ? sub { - Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller_pkg}${attribute}") - if $#_; - return $_[0]{$attribute}; - } - : sub { - return $#_ - ? $_[0]{$attribute} = $_[1] - : $_[0]{$attribute}; - }; - - $metadata_for{$caller}{$attribute} = (); - - if ( my $I = $args{isa} ) { - my $orig_I = $I; - my $type; - if ( $I =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { - $I = _nested_constraints($attribute, $1, $2); - } - $metadata_for{$caller}{$attribute}{isa} = [$orig_I, $I]; - my $orig_method = $method; - $method = sub { - if ( $#_ ) { - Mo::_check_type_constaints($attribute, $I, $orig_I, $_[1]); - } - goto &$orig_method; - }; - } - - if ( my $builder = $args{builder} ) { - my $original_method = $method; - $method = sub { - $#_ - ? goto &$original_method - : ! exists $_[0]{$attribute} - ? $_[0]{$attribute} = $_[0]->$builder - : goto &$original_method - }; - } - - if ( my $code = $args{default} ) { - Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") - unless ref($code) eq 'CODE'; - my $original_method = $method; - $method = sub { - $#_ - ? goto &$original_method - : ! exists $_[0]{$attribute} - ? $_[0]{$attribute} = $_[0]->$code - : goto &$original_method - }; - } - - if ( my $role = $args{does} ) { - my $original_method = $method; - $method = sub { - if ( $#_ ) { - Carp::confess(qq) - unless blessed($_[1]) && $_[1]->does($role) - } - goto &$original_method - }; - } - - if ( my $coercion = $args{coerce} ) { - $metadata_for{$caller}{$attribute}{coerce} = $coercion; - my $original_method = $method; - $method = sub { - if ( $#_ ) { - return $original_method->($_[0], $coercion->($_[1])) - } - goto &$original_method; - } - } - - $method = $options{$_}->($method, $attribute, @_) - for sort keys %options; - - *{ _glob_for "${caller}::$attribute" } = $method; - - if ( $args{required} ) { - $metadata_for{$caller}{$attribute}{required} = 1; - } - - if ($args{clearer}) { - *{ _glob_for "${caller}::$args{clearer}" } - = sub { delete shift->{$attribute} } - } - - if ($args{predicate}) { - *{ _glob_for "${caller}::$args{predicate}" } - = sub { exists shift->{$attribute} } - } - - if ($args{handles}) { - _has_handles($caller, $attribute, \%args); - } - - if (exists $args{init_arg}) { - $metadata_for{$caller}{$attribute}{init_arg} = $args{init_arg}; - } - } - }, - %exports, - ); - - $export_for{$caller} = [ keys %exports ]; - - for my $keyword ( keys %exports ) { - *{ _glob_for "${caller}::$keyword" } = $exports{$keyword} - } - *{ _glob_for "${caller}::extends" }{CODE}->( "Mo::Object" ) - unless @{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] }; -}; - -sub _check_type_constaints { - my ($attribute, $I, $I_name, $val) = @_; - ( ref($I) eq 'CODE' - ? $I->($val) - : (ref $val eq $I - || ($val && $val eq $I) - || (exists $TYPES{$I} && $TYPES{$I}->($val))) - ) - || Carp::confess( - qq - . qq - . (defined $val ? Mo::Dumper($val) : 'undef') ) -} - -sub _has_handles { - my ($caller, $attribute, $args) = @_; - my $handles = $args->{handles}; - - my $ref = ref $handles; - my $kv; - if ( $ref eq ref [] ) { - $kv = { map { $_,$_ } @{$handles} }; - } - elsif ( $ref eq ref {} ) { - $kv = $handles; - } - elsif ( $ref eq ref qr// ) { - Carp::confess("Cannot delegate methods based on a Regexp without a type constraint (isa)") - unless $args->{isa}; - my $target_class = $args->{isa}; - $kv = { - map { $_, $_ } - grep { $_ =~ $handles } - grep { !exists $Mo::Object::{$_} && $target_class->can($_) } - grep { $_ ne 'has' && $_ ne 'extends' } - keys %{ _stash_for $target_class } - }; - } - else { - Carp::confess("handles for $ref not yet implemented"); - } - - while ( my ($method, $target) = each %{$kv} ) { - my $name = _glob_for "${caller}::$method"; - Carp::confess("You cannot overwrite a locally defined method ($method) with a delegation") - if defined &$name; - - my ($target, @curried_args) = ref($target) ? @$target : $target; - *$name = sub { - my $self = shift; - my $delegate_to = $self->$attribute(); - my $error = "Cannot delegate $method to $target because the value of $attribute"; - Carp::confess("$error is not defined") unless $delegate_to; - Carp::confess("$error is not an object (got '$delegate_to')") - unless Scalar::Util::blessed($delegate_to) || (!ref($delegate_to) && $delegate_to->can($target)); - return $delegate_to->$target(@curried_args, @_); - } - } -} - -sub _nested_constraints { - my ($attribute, $aggregate_type, $type) = @_; - - my $inner_types; - if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { - $inner_types = _nested_constraints($1, $2); - } - else { - $inner_types = $TYPES{$type}; - } - - if ( $aggregate_type eq 'ArrayRef' ) { - return sub { - my ($val) = @_; - return unless ref($val) eq ref([]); - - if ($inner_types) { - for my $value ( @{$val} ) { - return unless $inner_types->($value) - } - } - else { - for my $value ( @{$val} ) { - return unless $value && ($value eq $type - || (Scalar::Util::blessed($value) && $value->isa($type))); - } - } - return 1; - }; - } - elsif ( $aggregate_type eq 'Maybe' ) { - return sub { - my ($value) = @_; - return 1 if ! defined($value); - if ($inner_types) { - return unless $inner_types->($value) - } - else { - return unless $value eq $type - || (Scalar::Util::blessed($value) && $value->isa($type)); - } - return 1; - } - } - else { - Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); - } -} - -sub _set_package_isa { - my ($package, @new_isa) = @_; - - *{ _glob_for "${package}::ISA" } = [@new_isa]; -} - -sub _set_inherited_metadata { - my $class = shift; - my $linearized_isa = mro::get_linear_isa($class); - my %new_metadata; - - for my $isa_class (reverse @$linearized_isa) { - %new_metadata = ( - %new_metadata, - %{ $metadata_for{$isa_class} || {} }, - ); - } - $metadata_for{$class} = \%new_metadata; -} - -sub unimport { - my $caller = scalar caller(); - my $stash = _stash_for( $caller ); - - delete $stash->{$_} for @{$export_for{$caller}}; -} - -sub Dumper { - require Data::Dumper; - local $Data::Dumper::Indent = 0; - local $Data::Dumper::Sortkeys = 0; - local $Data::Dumper::Quotekeys = 0; - local $Data::Dumper::Terse = 1; - - Data::Dumper::Dumper(@_) -} - -BEGIN { - if ($] >= 5.010) { - { local $@; require mro; } - } - else { - local $@; - eval { - require MRO::Compat; - } or do { - *mro::get_linear_isa = *mro::get_linear_isa_dfs = sub { - no strict 'refs'; - - my $classname = shift; - - my @lin = ($classname); - my %stored; - foreach my $parent (@{"$classname\::ISA"}) { - my $plin = mro::get_linear_isa_dfs($parent); - foreach (@$plin) { - next if exists $stored{$_}; - push(@lin, $_); - $stored{$_} = 1; - } - } - return \@lin; - }; - } - } -} - -} -1; -} -# ########################################################################### -# End Mo package -# ########################################################################### - # ########################################################################### # Transformers package # This package is a copy without comments from the original. The original @@ -6686,21 +6230,14 @@ sub best_algorithm { die "Invalid checksum algorithm $alg" if $alg && !$ALGOS{$alg}; - my $version = VersionParser->new($dbh); - if ( $args{where} || $args{chunk} # CHECKSUM does whole table - || $args{replicate} # CHECKSUM can't do INSERT.. SELECT - || $version < '4.1.1') # CHECKSUM doesn't exist + || $args{replicate}) # CHECKSUM can't do INSERT.. SELECT { PTDEBUG && _d('Cannot use CHECKSUM algorithm'); @choices = grep { $_ ne 'CHECKSUM' } @choices; } - if ( $version < '4.1.1' ) { - PTDEBUG && _d('Cannot use BIT_XOR algorithm because MySQL < 4.1.1'); - @choices = grep { $_ ne 'BIT_XOR' } @choices; - } if ( $alg && grep { $_ eq $alg } @choices ) { PTDEBUG && _d('User requested', $alg, 'algorithm'); @@ -7068,16 +6605,13 @@ sub sync_table { PTDEBUG && _d('CRC column:', $crc_col); my $index_hint; - my $hint = ((VersionParser->new($src->{dbh}) >= '4.0.9' - && VersionParser->new($dst->{dbh}) >= '4.0.9') ? 'FORCE' : 'USE') - . ' INDEX'; if ( $args{chunk_index} ) { PTDEBUG && _d('Using given chunk index for index hint'); - $index_hint = "$hint (" . $q->quote($args{chunk_index}) . ")"; + $index_hint = "FORCE INDEX (" . $q->quote($args{chunk_index}) . ")"; } elsif ( $plugin_args{chunk_index} && $args{index_hint} ) { PTDEBUG && _d('Using chunk index chosen by plugin for index hint'); - $index_hint = "$hint (" . $q->quote($plugin_args{chunk_index}) . ")"; + $index_hint = "FORCE INDEX (" . $q->quote($plugin_args{chunk_index}) . ")"; } PTDEBUG && _d('Index hint:', $index_hint); @@ -8560,200 +8094,6 @@ sub _d { # End MockSth package # ########################################################################### -# ########################################################################### -# VersionParser 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/VersionParser.pm -# t/lib/VersionParser.t -# See https://launchpad.net/percona-toolkit for more information. -# ########################################################################### -{ -package VersionParser; - -use Mo; -use Scalar::Util qw(blessed); -use English qw(-no_match_vars); -use constant PTDEBUG => $ENV{PTDEBUG} || 0; - -use overload ( - '""' => "version", - '<=>' => "cmp", - 'cmp' => "cmp", - fallback => 1, -); - -use Carp (); - -our $VERSION = 0.01; - -has major => ( - is => 'ro', - isa => 'Int', - required => 1, -); - -has [qw( minor revision )] => ( - is => 'ro', - isa => 'Num', -); - -has flavor => ( - is => 'ro', - isa => 'Str', - default => sub { 'Unknown' }, -); - -has innodb_version => ( - is => 'ro', - isa => 'Str', - default => sub { 'NO' }, -); - -sub series { - my $self = shift; - return $self->_join_version($self->major, $self->minor); -} - -sub version { - my $self = shift; - return $self->_join_version($self->major, $self->minor, $self->revision); -} - -sub is_in { - my ($self, $target) = @_; - - return $self eq $target; -} - -sub _join_version { - my ($self, @parts) = @_; - - return join ".", map { my $c = $_; $c =~ s/^0\./0/; $c } grep defined, @parts; -} -sub _split_version { - my ($self, $str) = @_; - my @version_parts = map { s/^0(?=\d)/0./; $_ } $str =~ m/(\d+)/g; - return @version_parts[0..2]; -} - -sub normalized_version { - my ( $self ) = @_; - my $result = sprintf('%d%02d%02d', map { $_ || 0 } $self->major, - $self->minor, - $self->revision); - PTDEBUG && _d($self->version, 'normalizes to', $result); - return $result; -} - -sub comment { - my ( $self, $cmd ) = @_; - my $v = $self->normalized_version(); - - return "/*!$v $cmd */" -} - -my @methods = qw(major minor revision); -sub cmp { - my ($left, $right) = @_; - my $right_obj = (blessed($right) && $right->isa(ref($left))) - ? $right - : ref($left)->new($right); - - my $retval = 0; - for my $m ( @methods ) { - last unless defined($left->$m) && defined($right_obj->$m); - $retval = $left->$m <=> $right_obj->$m; - last if $retval; - } - return $retval; -} - -sub BUILDARGS { - my $self = shift; - - if ( @_ == 1 ) { - my %args; - if ( blessed($_[0]) && $_[0]->can("selectrow_hashref") ) { - PTDEBUG && _d("VersionParser got a dbh, trying to get the version"); - my $dbh = $_[0]; - local $dbh->{FetchHashKeyName} = 'NAME_lc'; - my $query = eval { - $dbh->selectall_arrayref(q, { Slice => {} }) - }; - if ( $query ) { - $query = { map { $_->{variable_name} => $_->{value} } @$query }; - @args{@methods} = $self->_split_version($query->{version}); - $args{flavor} = delete $query->{version_comment} - if $query->{version_comment}; - } - elsif ( eval { ($query) = $dbh->selectrow_array('SELECT VERSION()') } ) { - @args{@methods} = $self->_split_version($query); - } - else { - Carp::confess("Couldn't get the version from the dbh while " - . "creating a VersionParser object: $@"); - } - $args{innodb_version} = eval { $self->_innodb_version($dbh) }; - } - elsif ( !ref($_[0]) ) { - @args{@methods} = $self->_split_version($_[0]); - } - - for my $method (@methods) { - delete $args{$method} unless defined $args{$method}; - } - @_ = %args if %args; - } - - return $self->SUPER::BUILDARGS(@_); -} - -sub _innodb_version { - my ( $self, $dbh ) = @_; - return unless $dbh; - my $innodb_version = "NO"; - - my ($innodb) = - grep { $_->{engine} =~ m/InnoDB/i } - map { - my %hash; - @hash{ map { lc $_ } keys %$_ } = values %$_; - \%hash; - } - @{ $dbh->selectall_arrayref("SHOW ENGINES", {Slice=>{}}) }; - if ( $innodb ) { - PTDEBUG && _d("InnoDB support:", $innodb->{support}); - if ( $innodb->{support} =~ m/YES|DEFAULT/i ) { - my $vars = $dbh->selectrow_hashref( - "SHOW VARIABLES LIKE 'innodb_version'"); - $innodb_version = !$vars ? "BUILTIN" - : ($vars->{Value} || $vars->{value}); - } - else { - $innodb_version = $innodb->{support}; # probably DISABLED or NO - } - } - - PTDEBUG && _d("InnoDB version:", $innodb_version); - return $innodb_version; -} - -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"; -} - -no Mo; -1; -} -# ########################################################################### -# End VersionParser package -# ########################################################################### - # ########################################################################### # ReportFormatter package # This package is a copy without comments from the original. The original @@ -11061,24 +10401,12 @@ sub main { } if ( $compare->{warnings} ) { - # SHOW WARNINGS requires MySQL 4.1. - my $have_warnings = 1; - foreach my $host ( @$hosts ) { - if ( VersionParser->new($host->{dbh}) < '4.1.0' ) { - warn "Compare warnings DISABLED because host ", $host->{name}, - " MySQL version is less than 4.1"; - $have_warnings = 0; - last; - } - } - if ( $have_warnings ) { - push @compare_modules, new CompareWarnings( - 'clear-warnings' => $o->get('clear-warnings'), - 'clear-warnings-table' => $o->get('clear-warnings-table'), - get_id => sub { return make_checksum(@_); }, - %common_modules, - ); - } + push @compare_modules, new CompareWarnings( + 'clear-warnings' => $o->get('clear-warnings'), + 'clear-warnings-table' => $o->get('clear-warnings-table'), + get_id => sub { return make_checksum(@_); }, + %common_modules, + ); } # ######################################################################## diff --git a/bin/pt-variable-advisor b/bin/pt-variable-advisor index 8d6a9670..db88f73d 100755 --- a/bin/pt-variable-advisor +++ b/bin/pt-variable-advisor @@ -1963,7 +1963,7 @@ sub BUILDARGS { my $dbh = $_[0]; local $dbh->{FetchHashKeyName} = 'NAME_lc'; my $query = eval { - $dbh->selectall_arrayref(q, { Slice => {} }) + $dbh->selectall_arrayref(q/SHOW VARIABLES LIKE 'version%'/, { Slice => {} }) }; if ( $query ) { $query = { map { $_->{variable_name} => $_->{value} } @$query }; @@ -1971,7 +1971,7 @@ sub BUILDARGS { $args{flavor} = delete $query->{version_comment} if $query->{version_comment}; } - elsif ( eval { ($query) = $dbh->selectrow_array('SELECT VERSION()') } ) { + elsif ( eval { ($query) = $dbh->selectrow_array(q/SELECT VERSION()/) } ) { @args{@methods} = $self->_split_version($query); } else { @@ -3300,10 +3300,10 @@ sub get_rules { my ( %args ) = @_; my $mysql_version = $args{mysql_version}; return 0 unless $mysql_version; - return 1 if ($mysql_version eq '3' && $mysql_version lt '3.23') - || ($mysql_version eq '4' && $mysql_version lt '4.1.20') - || ($mysql_version eq '5.0' && $mysql_version lt '5.0.37') - || ($mysql_version eq '5.1' && $mysql_version lt '5.1.30'); + return 1 if ($mysql_version == '3' && $mysql_version < '3.23' ) + || ($mysql_version == '4' && $mysql_version < '4.1.20') + || ($mysql_version == '5.0' && $mysql_version < '5.0.37') + || ($mysql_version == '5.1' && $mysql_version < '5.1.30'); return 0; }, }, @@ -3313,7 +3313,7 @@ sub get_rules { my ( %args ) = @_; my $mysql_version = $args{mysql_version}; return 0 unless $mysql_version; - return $mysql_version lt '5.1' ? 1 : 0; # 5.1.x + return $mysql_version < '5.1' ? 1 : 0; # 5.1.x }, }, }; From d8cef2e902515e9ac9a1a714424d0c22ec971dff Mon Sep 17 00:00:00 2001 From: Daniel Nichter Date: Thu, 19 Jul 2012 12:54:32 -0600 Subject: [PATCH 26/27] Merge 2.1 r316 and r317. --- sandbox/jenkins-test | 2 +- sandbox/stop-sandbox | 10 ++++++++-- sandbox/test-env | 12 ++++++------ 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/sandbox/jenkins-test b/sandbox/jenkins-test index afd51ac9..4b3d4cfe 100755 --- a/sandbox/jenkins-test +++ b/sandbox/jenkins-test @@ -63,7 +63,7 @@ export PATH="$PATH:/usr/sbin:$MYSQL_BASE_DIR/bin" ############################# sandbox/test-env checkconfig || exit 1 sandbox/test-env stop || exit 1 -sandbox/test-env kill +sandbox/test-env kill || exit 1 sandbox/test-env start || exit 1 ####################### diff --git a/sandbox/stop-sandbox b/sandbox/stop-sandbox index e9a10c25..68776034 100755 --- a/sandbox/stop-sandbox +++ b/sandbox/stop-sandbox @@ -17,8 +17,14 @@ for port in "$@"; do continue fi - /tmp/$port/stop - exit_status=$((exit_status | $?)) + if [ -x "/tmp/$port/stop" ]; then + /tmp/$port/stop + exit_status=$((exit_status | $?)) + else + echo "/tmp/$port is missing files:" >&2 + ls -la /tmp/$port >&2 + fi + rm -rf /tmp/$port exit_status=$((exit_status | $?)) done diff --git a/sandbox/test-env b/sandbox/test-env index fb71172e..cd1f94e5 100755 --- a/sandbox/test-env +++ b/sandbox/test-env @@ -184,7 +184,6 @@ sandbox_is_running() { kill_sandbox() { local p=$1 - local rmdir=1 # See if the sandbox server is running. sandbox_is_running $p @@ -194,7 +193,7 @@ kill_sandbox() { mysqladmin -h127.1 -P$p -umsandbox -pmsandbox shutdown >/dev/null 2>&1 mysqladmin -h127.1 -P$p -uroot -pmsandbox shutdown >/dev/null 2>&1 mysqladmin -h127.1 -P$p -uroot shutdown >/dev/null 2>&1 - sleep 1 + sleep 2 # See if the sandbox server is still running. sandbox_is_running $p @@ -204,13 +203,13 @@ kill_sandbox() { pid2=`ps xw | grep -v grep | grep -v mysqld_safe | grep mysqld | grep /tmp/$p | awk '{print $1}'` [ "$pid1" ] && kill -9 $pid1 # Die, damn you, die! [ "$pid2" ] && kill -9 $pid2 - sleep 1 + sleep 2 # Third and finaly check if the sandbox server is running. sandbox_is_running $p if [ $? -eq 0 ]; then err "Failed to kill MySQL test server on port $p (PID $pid1, $pid2)" - rmdir=0 + return 1 else echo "Killed MySQL test server on port $p (PID $pid1, $pid2)" fi @@ -219,12 +218,12 @@ kill_sandbox() { fi fi - if [ $rmdir -eq 1 ] && [ -d "/tmp/$p" ]; then + if [ -d "/tmp/$p" ]; then rm -rf /tmp/$p echo "Removed /tmp/$p" fi - return + return 0 } MYSQL_VERSION="" @@ -327,6 +326,7 @@ case $opt in # when a polite stop fails. It uses kill -9 as a last resort. for port in 12349 12348 12347 12346 12345 2903 2902 2901 2900; do kill_sandbox $port + exit_status=$((exit_status | $?)) done ;; restart) From adc02c767cc21d2d01750c24feb921df8d3baf71 Mon Sep 17 00:00:00 2001 From: Brian Fraser Date: Thu, 19 Jul 2012 18:38:35 -0300 Subject: [PATCH 27/27] Mo tests: Give everything a unique name --- t/lib/Mo/handles.t | 26 ++++----- t/lib/Mo/isa.t | 130 +++++++++++++++++++++++++++++++++------------ 2 files changed, 109 insertions(+), 47 deletions(-) diff --git a/t/lib/Mo/handles.t b/t/lib/Mo/handles.t index e8f74ccc..653c396f 100644 --- a/t/lib/Mo/handles.t +++ b/t/lib/Mo/handles.t @@ -185,9 +185,9 @@ is($car->stop, 'Engine::stop', '... got the right value from ->stop'); can_ok($baz_proxy, 'bar'); can_ok($baz_proxy, 'boo'); - is($baz_proxy->foo, 'Baz::foo', '... got the right proxied return value'); - is($baz_proxy->bar, 'Baz::bar', '... got the right proxied return value'); - is($baz_proxy->boo, 'Baz::boo', '... got the right proxied return value'); + is($baz_proxy->foo, 'Baz::foo', '... ->foo got the right proxied return value'); + is($baz_proxy->bar, 'Baz::bar', '... ->bar got the right proxied return value'); + is($baz_proxy->boo, 'Baz::boo', '... ->boo got the right proxied return value'); } { my $baz_proxy = Baz::Proxy2->new; @@ -199,8 +199,8 @@ is($car->stop, 'Engine::stop', '... got the right value from ->stop'); can_ok($baz_proxy, 'foo'); can_ok($baz_proxy, 'boo'); - is($baz_proxy->foo, 'Baz::foo', '... got the right proxied return value'); - is($baz_proxy->boo, 'Baz::boo', '... got the right proxied return value'); + is($baz_proxy->foo, 'Baz::foo', '... ->foo got the right proxied return value'); + is($baz_proxy->boo, 'Baz::boo', '... ->boo got the right proxied return value'); } { my $baz_proxy = Baz::Proxy3->new; @@ -212,8 +212,8 @@ is($car->stop, 'Engine::stop', '... got the right value from ->stop'); can_ok($baz_proxy, 'bar'); can_ok($baz_proxy, 'boo'); - is($baz_proxy->bar, 'Baz::bar', '... got the right proxied return value'); - is($baz_proxy->boo, 'Baz::boo', '... got the right proxied return value'); + is($baz_proxy->bar, 'Baz::bar', '... ->bar got the right proxied return value'); + is($baz_proxy->boo, 'Baz::boo', '... ->boo got the right proxied return value'); } # ------------------------------------------------------------------- @@ -349,8 +349,8 @@ is($car->stop, 'Engine::stop', '... got the right value from ->stop'); # and make sure the delegation picks it up - is($bar->foo->bar, 30, '... bar->foo->bar returned the right (changed) value'); - is($bar->foo_bar, 30, '... bar->foo_bar delegated correctly'); + is($bar->foo->bar, 30, '... bar->foo->bar returned the value changed by ->foo->bar()'); + is($bar->foo_bar, 30, '... bar->foo_bar getter delegated correctly'); # change the value through the delegation ... @@ -358,8 +358,8 @@ is($car->stop, 'Engine::stop', '... got the right value from ->stop'); # and make sure everyone sees it - is($bar->foo->bar, 50, '... bar->foo->bar returned the right (changed) value'); - is($bar->foo_bar, 50, '... bar->foo_bar delegated correctly'); + is($bar->foo->bar, 50, '... bar->foo->bar returned the value changed by ->foo_bar()'); + is($bar->foo_bar, 50, '... bar->foo_bar getter delegated correctly'); # change the object we are delegating too @@ -432,12 +432,12 @@ is($car->stop, 'Engine::stop', '... got the right value from ->stop'); my $i = Bar->new(foo => undef); local $@; eval { $i->foo_bar }; - like($@, qr/is not defined/, 'useful error from unblessed reference' ); + like($@, qr/is not defined/, 'useful error if delegating from undef' ); my $j = Bar->new(foo => []); local $@; eval { $j->foo_bar }; - like($@, qr/is not an object \(got 'ARRAY/, 'useful error from unblessed reference' ); + like($@, qr/is not an object \(got 'ARRAY/, '... or from an unblessed reference' ); my $k = Bar->new(foo => "Foo"); local $@; diff --git a/t/lib/Mo/isa.t b/t/lib/Mo/isa.t index e6a56e8f..39e4e19c 100644 --- a/t/lib/Mo/isa.t +++ b/t/lib/Mo/isa.t @@ -40,67 +40,129 @@ package main; my $foo = Foo::isa->new( myStr => "abcdefg" ); # Bool: -lives_ok { ok !$foo->myBool(undef) } "Bool attr set to undef"; -lives_ok { is $foo->myBool(1), 1 } "Bool attr set to 1"; +lives_ok { + ok !defined($foo->myBool(undef)), + "myBool set to undef" +} "Bool attr set to undef"; +lives_ok { + is $foo->myBool(1), 1, + "myBool set to 1" +} "Bool attr set to 1"; is $foo->myBool, 1, "new value of \$foo->myBool as expected"; -lives_ok { is $foo->myBool(1e0), 1 } "Bool attr set to 1e0"; +lives_ok { + is $foo->myBool(1e0), 1, + "myBool set to 1e0 becomes 1" +} "Bool attr set to 1e0"; dies_ok { $foo->myBool("1f0") } "Bool attr set to 1f0 dies"; -lives_ok { is $foo->myBool(""), "" } "Bool attr set to empty string"; +lives_ok { + is $foo->myBool(""), "", + "myBool set to an emptry string" +} "Bool attr set to empty string"; is $foo->myBool, "", "new value of \$foo->myBool as expected"; -lives_ok { is $foo->myBool(0), 0 } "Bool attr set to 0"; -lives_ok { is $foo->myBool(0.0), 0 } "Bool attr set to 0.0"; -lives_ok { is $foo->myBool(0e0), 0 } "Bool attr set to 0e0"; +lives_ok { + is $foo->myBool(0), 0, + "myBool set to 0" +} "Bool attr set to 0"; +lives_ok { + is $foo->myBool(0.0), 0, + "myBool set to 0.0 becomes 0" +} "Bool attr set to 0.0"; +lives_ok { + is $foo->myBool(0e0), 0, + "myBool set to 0e0 becomes 0" +} "Bool attr set to 0e0"; dies_ok { $foo->myBool("0.0") } "Bool attr set to stringy 0.0 dies"; # Bool tests from Mouse: open(my $FH, "<", $0) or die "Could not open $0 for the test"; -my $msg = q(Bool rejects anything which is not a 1 or 0 or "" or undef"); -lives_ok { $foo->myBool(0) } $msg; -lives_ok { $foo->myBool(1) } $msg; -dies_ok { $foo->myBool(100) } $msg; -lives_ok { $foo->myBool("") } $msg; -dies_ok { $foo->myBool("Foo") } $msg; -dies_ok { $foo->myBool([]) } $msg; -dies_ok { $foo->myBool({}) } $msg; -dies_ok { $foo->myBool(sub {}) } $msg; -dies_ok { $foo->myBool(\"") } $msg; -dies_ok { $foo->myBool(*STDIN) } $msg; -dies_ok { $foo->myBool(\*STDIN) } $msg; -dies_ok { $foo->myBool($FH) } $msg; -dies_ok { $foo->myBool(qr/../) } $msg; -dies_ok { $foo->myBool(bless {}, "Foo") } $msg; -lives_ok { $foo->myBool(undef) } $msg; +# Bool rejects anything which is not a 1 or 0 or "" or undef: +lives_ok { $foo->myBool(0) } "Bool lives with 0"; +lives_ok { $foo->myBool(1) } "Bool lives with 1"; +dies_ok { $foo->myBool(100) } "Bool dies with 100"; +lives_ok { $foo->myBool("") } "Bool lives with ''"; +dies_ok { $foo->myBool("Foo") } "Bool dies with a string"; +dies_ok { $foo->myBool([]) } "Bool dies with an arrayref"; +dies_ok { $foo->myBool({}) } "Bool dies with a hashref"; +dies_ok { $foo->myBool(sub {}) } "Bool dies with a coderef"; +dies_ok { $foo->myBool(\"") } "Bool dies with a scalar ref"; +dies_ok { $foo->myBool(*STDIN) } "Bool dies with a glob"; +dies_ok { $foo->myBool(\*STDIN) } "Bool dies with a globref"; +dies_ok { $foo->myBool($FH) } "Bool dies with a lexical filehandle"; +dies_ok { $foo->myBool(qr/../) } "Bool dies with a regex"; +dies_ok { $foo->myBool(bless {}, "Foo") } "Bool dies with an object"; +lives_ok { $foo->myBool(undef) } "Bool lives with undef"; # Num: -lives_ok { is $foo->myNum(5.5), 5.5 } "Num attr set to decimal"; +lives_ok { + is $foo->myNum(5.5), + 5.5, + "myNum was set to 5.5" +} "Num attr set to decimal"; is $foo->myNum, 5.5, "new value of \$foo->myNum as expected"; -lives_ok { is $foo->myNum(5), 5 } "Num attr set to integer"; -lives_ok { is $foo->myNum(5e0), 5 } "Num attr set to 5e0"; +lives_ok { + is $foo->myNum(5), + 5, + "myNum was set to 5" +} "Num attr set to integer"; +lives_ok { + is $foo->myNum(5e0), + 5, + "myNum was set to 5e0" +} "Num attr set to 5e0"; dies_ok { $foo->myBool("5f0") } "Bool attr set to 5f0 dies"; -lives_ok { is $foo->myNum("5.5"), 5.5 } "Num attr set to stringy decimal"; +lives_ok { + is $foo->myNum("5.5"), + 5.5, + "myNum was set to q<5.5>" +} "Num attr set to stringy decimal"; # Int: -lives_ok { is $foo->myInt(0), 0 } "Int attr set to 0"; -lives_ok { is $foo->myInt(1), 1 } "Int attr set to 1"; -lives_ok { is $foo->myInt(1e0), 1 } "Int attr set to 1e0"; +lives_ok { + is $foo->myInt(0), + 0, + "myInt was set to 0" +} "Int attr set to 0"; +lives_ok { + is $foo->myInt(1), + 1, + "myInt was set to 1" +} "Int attr set to 1"; +lives_ok { + is $foo->myInt(1e0), + 1, + "myInt was set to 1e0" +} "Int attr set to 1e0"; is $foo->myInt, 1, "new value of \$foo->myInt as expected"; dies_ok { $foo->myInt("") } "Int attr set to empty string dies"; dies_ok { $foo->myInt(5.5) } "Int attr set to decimal dies"; # Str: is $foo->myStr, "abcdefg", "Str passed to constructor accepted"; -lives_ok { is $foo->myStr("hijklmn"), "hijklmn" } "Str attr set to a string"; +lives_ok { + is $foo->myStr("hijklmn"), "hijklmn", + "myStr was set to a string", +} "Str attr set to a string"; is $foo->myStr, "hijklmn", "new value of \$foo->myStr as expected"; -lives_ok { is $foo->myStr(5.5), 5.5 } "Str attr set to a decimal value"; +lives_ok { + is $foo->myStr(5.5), 5.5, + "myStr was set to 5.5" +} "Str attr set to a decimal value"; # Class instance: -lives_ok { is $foo->myFoo($foo), $foo } "Class instance attr set to self"; +lives_ok { + is $foo->myFoo($foo), $foo, + "myFoo set to self" +} "Class instance attr set to self"; isa_ok $foo->myFoo, "Foo::isa", "new value of \$foo->myFoo as expected"; dies_ok { $foo->myFoo({}) } "Class instance attr set to hash dies"; # Class name: my $class = ref($foo); -lives_ok { is $foo->myFoo($class), $class } "Class instance attr set to classname"; +lives_ok { + is $foo->myFoo($class), + $class, + "myFoo set to a classname" +} "Class instance attr set to classname"; is $foo->myFoo, $class, "new value of \$foo->myFoo as expected"; # Refs: