Files
percona-toolkit/t/lib/ChangeHandler.t
Sveta Smirnova 5c999ca3e0 PT-2340 - Support MySQL 8.4
- Removed runtime.txt after discussion with Anastasia Alexandrova
- Added "use VersionParser" into tests in t/lib when needed
- Removed word master from tests for pt-archiver, pt-config-diff, pt-deadlock-logger, pt-duplicate-key-checker, pt-find, pt-fk-error-logger, pt-heartbeat, pt-index-usage, pt-ioprofile, pt-kill, pt-mysql-summary
- Removed word slave from tests for pt-archiver, pt-config-diff, pt-deadlock-logger, pt-duplicate-key-checker, pt-find, pt-fk-error-logger, pt-heartbeat, pt-index-usage, pt-ioprofile, pt-kill, pt-mysql-summary
- Updated modules for pt-archiver, pt-config-diff, pt-deadlock-logger, pt-duplicate-key-checker, pt-find, pt-fk-error-logger, pt-heartbeat, pt-index-usage, pt-ioprofile, pt-kill, pt-mysql-summary
- Changed mysql_ssl patch, so it is now short option s
- Added a check for existing zombies in t/pt-kill/execute_command.t
- Added bin/pt-galera-log-explainer to .gitignore
2024-07-27 01:59:52 +03:00

550 lines
16 KiB
Perl

#!/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;
use ChangeHandler;
use Quoter;
use DSNParser;
use VersionParser;
use Sandbox;
use PerconaTest;
my $dp = new DSNParser(opts => $dsn_opts);
my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp);
my $master_dbh = $sb->get_dbh_for('source');
my $slave1_dbh = $sb->get_dbh_for('replica1');
throws_ok(
sub { new ChangeHandler() },
qr/I need a Quoter/,
'Needs a Quoter',
);
my @rows;
my @dbhs;
my $q = new Quoter();
my $ch = new ChangeHandler(
Quoter => $q,
right_db => 'test', # dst
right_tbl => 'foo',
left_db => 'test', # src
left_tbl => 'test1',
actions => [ sub { push @rows, $_[0]; push @dbhs, $_[1]; } ],
replace => 0,
queue => 0,
);
$ch->change('INSERT', { a => 1, b => 2 }, [qw(a)] );
is_deeply(\@rows,
["INSERT INTO `test`.`foo`(`a`, `b`) VALUES ('1', '2')",],
'First row',
);
$ch->change(undef, { a => 1, b => 2 }, [qw(a)] );
is_deeply(
\@rows,
["INSERT INTO `test`.`foo`(`a`, `b`) VALUES ('1', '2')",],
'Skips undef action'
);
is_deeply(\@rows,
["INSERT INTO `test`.`foo`(`a`, `b`) VALUES ('1', '2')",],
'First row',
);
$ch->{queue} = 1;
$ch->change('DELETE', { a => 1, b => 2 }, [qw(a)] );
is_deeply(\@rows,
["INSERT INTO `test`.`foo`(`a`, `b`) VALUES ('1', '2')",],
'Second row not there yet',
);
$ch->process_rows(1);
is_deeply(\@rows,
[
"INSERT INTO `test`.`foo`(`a`, `b`) VALUES ('1', '2')",
"DELETE FROM `test`.`foo` WHERE `a`='1' LIMIT 1",
],
'Second row there',
);
$ch->{queue} = 2;
$ch->change('UPDATE', { a => 1, b => 2 }, [qw(a)] );
$ch->process_rows(1);
is_deeply(\@rows,
[
"INSERT INTO `test`.`foo`(`a`, `b`) VALUES ('1', '2')",
"DELETE FROM `test`.`foo` WHERE `a`='1' LIMIT 1",
],
'Third row not there',
);
$ch->process_rows();
is_deeply(\@rows,
[
"INSERT INTO `test`.`foo`(`a`, `b`) VALUES ('1', '2')",
"DELETE FROM `test`.`foo` WHERE `a`='1' LIMIT 1",
"UPDATE `test`.`foo` SET `b`='2' WHERE `a`='1' LIMIT 1",
],
'All rows',
);
is_deeply(
{ $ch->get_changes() },
{ REPLACE => 0, DELETE => 1, INSERT => 1, UPDATE => 1 },
'Changes were recorded',
);
# #############################################################################
# Test that the optional dbh is passed through to our actions.
# #############################################################################
@rows = ();
@dbhs = ();
$ch->{queue} = 0;
# 42 is a placeholder for the dbh arg.
$ch->change('INSERT', { a => 1, b => 2 }, [qw(a)], 42);
is_deeply(
\@dbhs,
[42],
'dbh passed through change()'
);
$ch->{queue} = 1;
@rows = ();
@dbhs = ();
$ch->change('INSERT', { a => 1, b => 2 }, [qw(a)], 42);
is_deeply(
\@dbhs,
[],
'No dbh yet'
);
$ch->process_rows(1);
is_deeply(
\@dbhs,
[42],
'dbh passed through process_rows()'
);
# #############################################################################
# Test switching direction (swap src/dst).
# #############################################################################
$ch = new ChangeHandler(
Quoter => $q,
left_db => 'test',
left_tbl => 'left_foo',
right_db => 'test',
right_tbl => 'right_foo',
actions => [ sub { push @rows, $_[0]; push @dbhs, $_[1]; } ],
replace => 0,
queue => 0,
);
@rows = ();
@dbhs = ();
# Default is left=source.
$ch->set_src('right');
is(
$ch->src,
'`test`.`right_foo`',
'Changed src',
);
is(
$ch->dst,
'`test`.`left_foo`',
'Changed dst'
);
$ch->change('INSERT', { a => 1, b => 2 }, [qw(a)] );
is_deeply(
\@rows,
["INSERT INTO `test`.`left_foo`(`a`, `b`) VALUES ('1', '2')",],
'INSERT new dst',
);
$ch->change('DELETE', { a => 1, b => 2 }, [qw(a)] );
$ch->process_rows(1);
is_deeply(\@rows,
[
"INSERT INTO `test`.`left_foo`(`a`, `b`) VALUES ('1', '2')",
"DELETE FROM `test`.`left_foo` WHERE `a`='1' LIMIT 1",
],
'DELETE new dst',
);
# #############################################################################
# Test fetch_back().
# #############################################################################
SKIP: {
skip 'Cannot connect to sandbox master', 1 unless $master_dbh;
$master_dbh->do('DROP DATABASE IF EXISTS test');
$master_dbh->do('CREATE DATABASE IF NOT EXISTS test');
$ch = new ChangeHandler(
Quoter => $q,
right_db => 'test', # dst
right_tbl => 'foo',
left_db => 'test', # src
left_tbl => 'test1',
actions => [ sub { push @rows, $_[0]; push @dbhs, $_[1]; } ],
replace => 0,
queue => 0,
);
@rows = ();
$ch->{queue} = 0;
$ch->fetch_back($master_dbh);
`/tmp/12345/use < $trunk/t/lib/samples/before-TableSyncChunk.sql`;
# This should cause it to fetch the row from test.test1 where a=1
$ch->change('UPDATE', { a => 1, __foo => 'bar' }, [qw(a)] );
$ch->change('DELETE', { a => 1, __foo => 'bar' }, [qw(a)] );
# TODO: Sometimes this is retrieved as (b, a) instead of (a, b)
#$ch->change('INSERT', { a => 1, __foo => 'bar' }, [qw(a)] );
is_deeply(
\@rows,
[
"UPDATE `test`.`foo` SET `b`='en' WHERE `a`='1' LIMIT 1",
"DELETE FROM `test`.`foo` WHERE `a`='1' LIMIT 1",
# "INSERT INTO `test`.`foo`(`a`, `b`) VALUES ('1', 'en')",
],
'Fetch-back',
);
}
# #############################################################################
# Issue 371: Make mk-table-sync preserve column order in SQL
# #############################################################################
my $row = {
id => 1,
foo => 'foo',
bar => 'bar',
};
my $tbl_struct = {
col_posn => { id=>0, foo=>1, bar=>2 },
};
$ch = new ChangeHandler(
Quoter => $q,
right_db => 'test', # dst
right_tbl => 'issue_371',
left_db => 'test', # src
left_tbl => 'issue_371',
actions => [ sub { push @rows, @_ } ],
replace => 0,
queue => 0,
tbl_struct => $tbl_struct,
);
@rows = ();
@dbhs = ();
is(
$ch->make_INSERT($row, [qw(id foo bar)]),
"INSERT INTO `test`.`issue_371`(`id`, `foo`, `bar`) VALUES ('1', 'foo', 'bar')",
'make_INSERT() preserves column order'
);
is(
$ch->make_REPLACE($row, [qw(id foo bar)]),
"REPLACE INTO `test`.`issue_371`(`id`, `foo`, `bar`) VALUES ('1', 'foo', 'bar')",
'make_REPLACE() preserves column order'
);
is(
$ch->make_UPDATE($row, [qw(id foo)]),
"UPDATE `test`.`issue_371` SET `bar`='bar' WHERE `id`='1' AND `foo`='foo' LIMIT 1",
'make_UPDATE() preserves column order'
);
is(
$ch->make_DELETE($row, [qw(id foo bar)]),
"DELETE FROM `test`.`issue_371` WHERE `id`='1' AND `foo`='foo' AND `bar`='bar' LIMIT 1",
'make_DELETE() preserves column order'
);
# Test what happens if the row has a column that not in the tbl struct.
$row->{other_col} = 'zzz';
is(
$ch->make_INSERT($row, [qw(id foo bar)]),
"INSERT INTO `test`.`issue_371`(`id`, `foo`, `bar`, `other_col`) VALUES ('1', 'foo', 'bar', 'zzz')",
'make_INSERT() preserves column order, with col not in tbl'
);
is(
$ch->make_REPLACE($row, [qw(id foo bar)]),
"REPLACE INTO `test`.`issue_371`(`id`, `foo`, `bar`, `other_col`) VALUES ('1', 'foo', 'bar', 'zzz')",
'make_REPLACE() preserves column order, with col not in tbl'
);
is(
$ch->make_UPDATE($row, [qw(id foo)]),
"UPDATE `test`.`issue_371` SET `bar`='bar', `other_col`='zzz' WHERE `id`='1' AND `foo`='foo' LIMIT 1",
'make_UPDATE() preserves column order, with col not in tbl'
);
delete $row->{other_col};
SKIP: {
skip 'Cannot connect to sandbox master', 3 unless $master_dbh;
$master_dbh->do('DROP TABLE IF EXISTS test.issue_371');
$master_dbh->do('CREATE TABLE test.issue_371 (id INT, foo varchar(16), bar char)');
$master_dbh->do("INSERT INTO test.issue_371 VALUES (1,'foo','a'),(2,'bar','b')");
$ch->fetch_back($master_dbh);
is(
$ch->make_INSERT($row, [qw(id foo)]),
"INSERT INTO `test`.`issue_371`(`id`, `foo`, `bar`) VALUES ('1', 'foo', 'a')",
'make_INSERT() preserves column order, with fetch-back'
);
is(
$ch->make_REPLACE($row, [qw(id foo)]),
"REPLACE INTO `test`.`issue_371`(`id`, `foo`, `bar`) VALUES ('1', 'foo', 'a')",
'make_REPLACE() preserves column order, with fetch-back'
);
is(
$ch->make_UPDATE($row, [qw(id foo)]),
"UPDATE `test`.`issue_371` SET `bar`='a' WHERE `id`='1' AND `foo`='foo' LIMIT 1",
'make_UPDATE() preserves column order, with fetch-back'
);
};
# #############################################################################
# Issue 641: Make mk-table-sync use hex for binary/blob data
# #############################################################################
$tbl_struct = {
cols => [qw(a x b)],
type_for => {a=>'int', x=>'blob', b=>'varchar'},
};
$ch = new ChangeHandler(
Quoter => $q,
left_db => 'test',
left_tbl => 'lt',
right_db => 'test',
right_tbl => 'rt',
actions => [ sub {} ],
replace => 0,
queue => 0,
tbl_struct => $tbl_struct,
);
is(
$ch->make_fetch_back_query('1=1'),
"SELECT `a`, IF(BINARY(`x`)='', '', CONCAT('0x', HEX(`x`))) AS `x`, `b` FROM `test`.`lt` WHERE 1=1 LIMIT 1",
"Wraps BLOB column in CONCAT('0x', HEX(col)) AS col"
);
$ch = new ChangeHandler(
Quoter => $q,
left_db => 'test',
left_tbl => 'lt',
right_db => 'test',
right_tbl => 'rt',
actions => [ sub {} ],
replace => 0,
queue => 0,
hex_blob => 0,
tbl_struct => $tbl_struct,
);
is(
$ch->make_fetch_back_query('1=1'),
"SELECT `a`, `x`, `b` FROM `test`.`lt` WHERE 1=1 LIMIT 1",
"Disable blob hexing"
);
# #############################################################################
# Issue 1052: mk-table-sync inserts "0x" instead of "" for empty blob and text
# column values
# #############################################################################
$tbl_struct = {
cols => [qw(t)],
type_for => {t=>'blob'},
};
$ch = new ChangeHandler(
Quoter => $q,
left_db => 'test',
left_tbl => 't',
right_db => 'test',
right_tbl => 't',
actions => [ sub {} ],
replace => 0,
queue => 0,
tbl_struct => $tbl_struct,
);
is(
$ch->make_fetch_back_query('1=1'),
"SELECT IF(BINARY(`t`)='', '', CONCAT('0x', HEX(`t`))) AS `t` FROM `test`.`t` WHERE 1=1 LIMIT 1",
"Don't prepend 0x to blank blob/text column value (issue 1052)"
);
# #############################################################################
# An update to the above bug; It should only hexify for blob and binary, not
# for text columns; The latter not only breaks for UTF-8 data, but also
# breaks now that hex-looking columns aren't automatically left unquoted.
# #############################################################################
$tbl_struct = {
cols => [qw(t)],
type_for => {t=>'text'},
};
$ch = new ChangeHandler(
Quoter => $q,
left_db => 'test',
left_tbl => 't',
right_db => 'test',
right_tbl => 't',
actions => [ sub {} ],
replace => 0,
queue => 0,
tbl_struct => $tbl_struct,
);
is(
$ch->make_fetch_back_query('1=1'),
"SELECT `t` FROM `test`.`t` WHERE 1=1 LIMIT 1",
"Don't prepend 0x to blank blob/text column value (issue 1052)"
);
# #############################################################################
SKIP: {
skip 'Cannot connect to sandbox master', 1 unless $master_dbh;
$sb->load_file('source', "t/lib/samples/issue_641.sql");
@rows = ();
$tbl_struct = {
cols => [qw(id b)],
col_posn => {id=>0, b=>1},
type_for => {id=>'int', b=>'blob'},
};
$ch = new ChangeHandler(
Quoter => $q,
left_db => 'issue_641',
left_tbl => 'lt',
right_db => 'issue_641',
right_tbl => 'rt',
actions => [ sub { push @rows, $_[0]; } ],
replace => 0,
queue => 0,
tbl_struct => $tbl_struct,
);
$ch->fetch_back($master_dbh);
$ch->change('UPDATE', {id=>1}, [qw(id)] );
$ch->change('INSERT', {id=>1}, [qw(id)] );
is_deeply(
\@rows,
[
"UPDATE `issue_641`.`rt` SET `b`=0x089504E470D0A1A0A0000000D4948445200000079000000750802000000E55AD965000000097048597300000EC300000EC301C76FA8640000200049444154789C4CBB7794246779FFBBF78F7B7EBE466177677772CE3D9D667AA67BA62776CE39545557CE3974EE9EB049AB9556392210414258083 WHERE `id`='1' LIMIT 1",
"INSERT INTO `issue_641`.`rt`(`id`, `b`) VALUES ('1', 0x089504E470D0A1A0A0000000D4948445200000079000000750802000000E55AD965000000097048597300000EC300000EC301C76FA8640000200049444154789C4CBB7794246779FFBBF78F7B7EBE466177677772CE3D9D667AA67BA62776CE39545557CE3974EE9EB049AB9556392210414258083)",
],
"UPDATE and INSERT binary data as hex"
);
}
# #############################################################################
# Issue 387: More useful comments in mk-table-sync statements
# #############################################################################
@rows = ();
$ch = new ChangeHandler(
Quoter => $q,
right_db => 'test', # dst
right_tbl => 'foo',
left_db => 'test', # src
left_tbl => 'test1',
actions => [ sub { push @rows, $_[0]; push @dbhs, $_[1]; } ],
replace => 0,
queue => 1,
);
$ch->change('INSERT', { a => 1, b => 2 }, [qw(a)] );
$ch->process_rows(1, "trace");
is_deeply(
\@rows,
["INSERT INTO `test`.`foo`(`a`, `b`) VALUES ('1', '2') /*percona-toolkit trace*/",],
"process_rows() appends trace msg to SQL statements"
);
# #############################################################################
# ChangeHandler doesn't quote varchar columns with hex-looking values
# https://bugs.launchpad.net/percona-toolkit/+bug/1038276
# #############################################################################
SKIP: {
skip 'Cannot connect to sandbox master', 1 unless $master_dbh;
$sb->load_file('source', "t/lib/samples/bug_1038276.sql");
@rows = ();
$tbl_struct = {
cols => [qw(id b)],
col_posn => {id=>0, b=>1},
type_for => {id=>'int', b=>'varchar'},
};
$ch = new ChangeHandler(
Quoter => $q,
left_db => 'bug_1038276',
left_tbl => 'lt',
right_db => 'bug_1038276',
right_tbl => 'rt',
actions => [ sub { push @rows, $_[0]; } ],
replace => 0,
queue => 0,
tbl_struct => $tbl_struct,
);
$ch->fetch_back($master_dbh);
$ch->change('UPDATE', {id=>1}, [qw(id)] );
$ch->change('INSERT', {id=>1}, [qw(id)] );
is_deeply(
\@rows,
[
"UPDATE `bug_1038276`.`rt` SET `b`='0x89504E470D0A1A0A0000000D4948445200000079000000750802000000E55AD965000000097048597300000EC300000EC301C76FA8640000200049444154789C4CBB7794246779FFBBF78F7B7EBE466177677772CE3D9D667AA67BA62776CE39545557CE3974EE9EB049AB9556392210414258083' WHERE `id`='1' LIMIT 1",
"INSERT INTO `bug_1038276`.`rt`(`id`, `b`) VALUES ('1', '0x89504E470D0A1A0A0000000D4948445200000079000000750802000000E55AD965000000097048597300000EC300000EC301C76FA8640000200049444154789C4CBB7794246779FFBBF78F7B7EBE466177677772CE3D9D667AA67BA62776CE39545557CE3974EE9EB049AB9556392210414258083')",
],
"UPDATE and INSERT quote data regardless of how it looks if tbl_struct->quote_val is true"
);
}
# #############################################################################
# Done.
# #############################################################################
$sb->wipe_clean($master_dbh);
$sb->wipe_clean($slave1_dbh);
$sb->ok();
ok($sb->ok(), "Sandbox servers") or BAIL_OUT(__FILE__ . " broke the sandbox");
done_testing;