Files
percona-toolkit/lib/MySQLConfigComparer.pm
Viktor Szépe 2bd40d8c39 Remove trailing spaces (#665)
* Remove trailing spaces

* PR-665 -  Remove trailing spaces

- Updated not stable test t/pt-online-schema-change/preserve_triggers.t
- Updated utilities in bin directory

* PR-665 -  Remove trailing spaces

- Fixed typos

* PR-665 -  Remove trailing spaces

- Fixed typos

---------

Co-authored-by: Sveta Smirnova <sveta.smirnova@percona.com>
2023-09-06 01:15:12 +03:00

339 lines
11 KiB
Perl

# This program is copyright 2010-2011 Percona Ireland Ltd.
# 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.
# ###########################################################################
# MySQLConfigComparer package
# ###########################################################################
{
# Package: MySQLConfigComparer
# MySQLConfigComparer compares and diffs C<MySQLConfig> objects.
package MySQLConfigComparer;
use strict;
use warnings FATAL => 'all';
use English qw(-no_match_vars);
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
# Alternate values because a config file can have var=ON and then be shown
# in SHOW VARS as var=TRUE. I.e. there's several synonyms for basic
# true (1) and false (0), so we normalize them to make comparisons easier.
my %alt_val_for = (
ON => 1,
YES => 1,
TRUE => 1,
OFF => 0,
NO => 0,
FALSE => 0,
);
# Sub: new
#
# Parameters:
# %args - Arguments
#
# Optional Arguments:
# ignore_variables - Arrayref of variables to ignore
# numeric_variables - Arrayref of variables to compare numerically
# optional_value_variables - Arrayref of vars whose val is optional
# any_value_is_true_variables - Arrayref of vars... see below
# base_path - Hashref of variable=>base_path
#
# Returns:
# MySQLConfigComparer object
sub new {
my ( $class, %args ) = @_;
# These vars don't interest us so we ignore them.
my %ignore_vars = (
date_format => 1,
datetime_format => 1,
ft_stopword_file => 1,
timestamp => 1,
time_format => 1,
($args{ignore_variables}
? map { $_ => 1 } @{$args{ignore_variables}}
: ()),
);
# The vars should be compared with == instead of eq so that
# 0 equals 0.0, etc.
my %is_numeric = (
long_query_time => 1,
($args{numeric_variables}
? map { $_ => 1 } @{$args{numeric_variables}}
: ()),
);
# These vars can be specified like --log-error or --log-error=file in config
# files. If specified without a value, then they're "equal" to whatever
# default value SHOW VARIABLES lists.
my %value_is_optional = (
log_error => 1,
log_isam => 1,
secure_file_priv => 1,
($args{optional_value_variables}
? map { $_ => 1 } @{$args{optional_value_variables}}
: ()),
);
# Like value_is_optional but SHOW VARIABlES does not list a default value,
# it only lists ON if the variable was given in a config file without or
# without a value (e.g. --log or --log=file). So any value from the config
# file that's true (i.e. not a blank string) equals ON from SHOW VARIABLES.
my %any_value_is_true = (
log => 1,
log_bin => 1,
log_slow_queries => 1,
($args{any_value_is_true_variables}
? map { $_ => 1 } @{$args{any_value_is_true_variables}}
: ()),
);
# The value of these vars are relative to some base path. In config files
# just a filename can be given, but in SHOW VARS the full /base/path/filename
# is shown. So we have to qualify the config value with the correct
# base path.
my %base_path = (
character_sets_dir => 'basedir',
datadir => 'basedir',
general_log_file => 'datadir',
language => 'basedir',
log_error => 'datadir',
pid_file => 'datadir',
plugin_dir => 'basedir',
slow_query_log_file => 'datadir',
socket => 'datadir',
($args{base_paths}
? map { $_ => 1 } @{$args{base_paths}}
: ()),
);
my $self = {
ignore_vars => \%ignore_vars,
is_numeric => \%is_numeric,
value_is_optional => \%value_is_optional,
any_value_is_true => \%any_value_is_true,
base_path => \%base_path,
ignore_case => exists $args{ignore_case}
? $args{ignore_case}
: 1,
};
return bless $self, $class;
}
# Sub: diff
# Diff the variable values of <MySQLConfig> objects. Only the common
# set of variables (i.e. the vars that all configs have) are compared.
#
# Parameters:
# %args - Arguments
#
# Required Arguments:
# configs - Arrayref of <MySQLConfig> objects
#
# Returns:
# Hashref of variables that have different values, like
# (start code)
# {
# max_connections => [ 100, 50 ]
# }
# (end code)
# The arrayref vals correspond to the C<MySQLConfig> objects, so
# $diff->{var}->[N] is $configs->[N]->value_of(var).
sub diff {
my ( $self, %args ) = @_;
my @required_args = qw(configs);
foreach my $arg( @required_args ) {
die "I need a $arg argument" unless $args{$arg};
}
my ($configs) = @args{@required_args};
if ( @$configs < 2 ) {
PTDEBUG && _d("Less than two MySQLConfig objects; nothing to compare");
return;
}
my $base_path = $self->{base_path};
my $is_numeric = $self->{is_numeric};
my $any_value_is_true = $self->{any_value_is_true};
my $value_is_optional = $self->{value_is_optional};
# Get the vars that exist in all configs minus the ones we want to ignore.
my $config0 = $configs->[0];
my $last_config = @$configs - 1;
my $vars = $self->_get_shared_vars(%args);
my $ignore_case = $self->{ignore_case};
# Compare variables from first config (config0) to other configs (configN).
my $diffs;
VARIABLE:
foreach my $var ( @$vars ) {
my $is_dir = $var =~ m/dir$/ || $var eq 'language';
my $val0 = $self->_normalize_value( # config0 value
value => $config0->value_of($var),
is_directory => $is_dir,
base_path => $config0->value_of($base_path->{$var}) || "",
);
eval {
CONFIG:
foreach my $configN ( @$configs[1..$last_config] ) {
my $valN = $self->_normalize_value( # configN value
value => $configN->value_of($var),
is_directory => $is_dir,
base_path => $configN->value_of($base_path->{$var}) || "",
);
if ( $is_numeric->{$var} ) {
next CONFIG if $val0 == $valN;
}
else {
next CONFIG if $ignore_case
? lc($val0) eq lc($valN)
: $val0 eq $valN;
# Special rules apply when comparing different inputs/formats,
# e.g. when comparing an option file to SHOW VARIABLES. This
# is because certain difference are actually equal in different
# formats.
if ( $config0->format() ne $configN->format() ) {
if ( $any_value_is_true->{$var} ) {
next CONFIG if $val0 && $valN;
}
if ( $value_is_optional->{$var} ) {
next CONFIG if (!$val0 && $valN) || ($val0 && !$valN);
}
}
}
# We reach here if no comparison above was true and skipped
# to the next CONFIG. So reaching here means the values are
# different. We save the real, not-normalized values.
PTDEBUG && _d("Different", $var, "values:", $val0, $valN);
$diffs->{$var} = [ map { $_->value_of($var) } @$configs ];
last CONFIG;
} # CONFIG
};
if ( $EVAL_ERROR ) {
my $vals = join(', ',
map {
my $val = $_->value_of($var);
defined $val ? $val : 'undef'
} @$configs);
warn "Comparing $var values ($vals) caused an error: $EVAL_ERROR";
}
} # VARIABLE
return $diffs;
}
# Sub: missing
# Return variables that aren't in all the given <MySQLConfig> objects.
#
# Parameters:
# %args - Arguments
#
# Required Arguments:
# configs - Arrayref of C<MySQLConfig> objects
#
# Returns:
# Hashref of missing variables like,
# (start code)
# {
# query_cache_size => [0, 1]
# }
# (end code)
# The arrayref vals correspond to the C<MySQLConfig> objects, so
# $missing->{var}->[N] is $configs->[N]; the values are boolean:
# 1 means the C<MySQLConfig> obj has the variable, 0 means it doesn't.
sub missing {
my ( $self, %args ) = @_;
my @required_args = qw(configs);
foreach my $arg( @required_args ) {
die "I need a $arg argument" unless $args{$arg};
}
my ($configs) = @args{@required_args};
if ( @$configs < 2 ) {
PTDEBUG && _d("Less than two MySQLConfig objects; nothing to compare");
return;
}
# Get a unique list of all vars from all configs.
my %vars = map { $_ => 1 } map { keys %{$_->variables()} } @$configs;
my $missing;
foreach my $var ( keys %vars ) {
# If the number of configs having the var is less than the number of
# configs, then one of the configs must be missing the variable.
my $n_configs_having_var = grep { $_->has($var) } @$configs;
if ( $n_configs_having_var < @$configs ) {
$missing->{$var} = [ map { $_->has($var) ? 1 : 0 } @$configs ];
}
}
return $missing;
}
sub _normalize_value {
my ( $self, %args ) = @_;
my ($val, $is_dir, $base_path) = @args{qw(value is_directory base_path)};
$val = defined $val ? $val : '';
$val = $alt_val_for{$val} if exists $alt_val_for{$val};
if ( $val ) {
if ( $is_dir ) {
$val .= '/' unless $val =~ m/\/$/;
}
if ( $base_path && $val !~ m/^\// ) {
$val =~ s/^\.?(.+)/$base_path\/$1/; # prepend base path
$val =~ s/\/{2,}/\//g; # make redundant // single /
}
}
return $val;
}
sub _get_shared_vars {
my ( $self, %args ) = @_;
my ($configs) = @args{qw(configs)};
my $ignore_vars = $self->{ignore_vars};
my $config0 = $configs->[0];
my $last_config = @$configs - 1;
my @vars
= grep { !$ignore_vars->{$_} }
map {
my $config = $_;
my $vars = $config->variables();
grep { $config0->has($_); } keys %$vars;
} @$configs[1..$last_config];
return \@vars;
}
sub _d {
my ($package, undef, $line) = caller 0;
@_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
map { defined $_ ? $_ : 'undef' }
@_;
print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
}
1;
}
# ###########################################################################
# End MySQLConfigComparer package
# ###########################################################################