Files
percona-toolkit/t/lib/TableParser.t
Sveta Smirnova 194bb99ef9 PT-2340 - Support MySQL 8.4
- Fixed 5.7 tests
2024-08-25 04:36:37 +03:00

1354 lines
44 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 Text::Diff;
use TableParser;
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 $dbh = $sb->get_dbh_for('source');
my $q = new Quoter();
my $tp = new TableParser(Quoter=>$q);
my $tbl;
my $sample = "t/lib/samples/tables/";
my $transform_int = undef;
# In version 8.0 integer display width is deprecated and not shown in the outputs.
# So we need to transform our samples.
if ($sandbox_version ge '8.0') {
$transform_int = sub {
my $txt = slurp_file(shift);
$txt =~ s/int\(\d{1,2}\)/int/g;
$txt =~ s/utf8/utf8mb3/g;
print $txt;
};
}
SKIP: {
skip "Cannot connect to sandbox master", 2 unless $dbh;
skip 'Sandbox master does not have the sakila database', 2
unless @{$dbh->selectcol_arrayref("SHOW DATABASES LIKE 'sakila'")};
eval { $tp->get_create_table($dbh, 'sakila', 'FOO') };
ok(
$EVAL_ERROR,
"get_create_table(nonexistent table) dies"
);
my $ddl = $tp->get_create_table($dbh, 'sakila', 'actor');
if ( $ddl =~ m/TABLE "actor"/ ) { # It's ANSI quoting, compensate
$ddl = $tp->ansi_to_legacy($ddl);
$ddl = "$ddl ENGINE=InnoDB AUTO_INCREMENT=201 DEFAULT CHARSET=utf8";
}
ok(
no_diff(
"$ddl\n",
$sandbox_version ge '5.1' ? "$sample/sakila.actor"
: "$sample/sakila.actor-5.0",
cmd_output => 1,
transform_sample => $transform_int
),
"get_create_table(sakila.actor)"
);
# Bug 932442: column with 2 spaces
$sb->load_file('source', "t/pt-table-checksum/samples/2-space-col.sql");
$ddl = $tp->get_create_table($dbh, qw(test t));
like(
$ddl,
qr/[`"]a b[`"]\s+/,
"Does not compress spaces (bug 932442)"
);
};
eval {
$tp->parse( load_file('t/lib/samples/noquotes.sql') );
};
like($EVAL_ERROR, qr/quoting/, 'No quoting');
$tbl = $tp->parse( load_file('t/lib/samples/t1.sql') );
is_deeply(
$tbl,
{ cols => [qw(a)],
col_posn => { a => 0 },
is_col => { a => 1 },
is_autoinc => { a => 0 },
null_cols => [qw(a)],
is_generated => {},
non_generated_cols => [ 'a' ],
is_nullable => { a => 1 },
clustered_key => undef,
keys => {},
defs => { a => ' `a` int(11) default NULL' },
numeric_cols => [qw(a)],
is_numeric => { a => 1 },
engine => 'MyISAM',
type_for => { a => 'int' },
name => 't1',
charset => 'latin1',
},
'Basic table is OK',
);
$tbl = $tp->parse( load_file('t/lib/samples/TableParser-prefix_idx.sql') );
is_deeply(
$tbl,
{
name => 't1',
cols => [ 'a', 'b' ],
non_generated_cols => [ 'a', 'b' ],
col_posn => { a => 0, b => 1 },
is_col => { a => 1, b => 1 },
is_autoinc => { 'a' => 0, 'b' => 0 },
is_generated => {},
null_cols => [ 'a', 'b' ],
is_nullable => { 'a' => 1, 'b' => 1 },
clustered_key => undef,
keys => {
prefix_idx => {
is_unique => 0,
is_col => {
a => 1,
b => 1,
},
name => 'prefix_idx',
type => 'BTREE',
is_nullable => 2,
colnames => '`a`(10),`b`(20)',
cols => [ 'a', 'b' ],
col_prefixes => [ 10, 20 ],
ddl => 'KEY `prefix_idx` (`a`(10),`b`(20)),',
},
mix_idx => {
is_unique => 0,
is_col => {
a => 1,
b => 1,
},
name => 'mix_idx',
type => 'BTREE',
is_nullable => 2,
colnames => '`a`,`b`(20)',
cols => [ 'a', 'b' ],
col_prefixes => [ undef, 20 ],
ddl => 'KEY `mix_idx` (`a`,`b`(20))',
},
},
defs => {
a => ' `a` varchar(64) default NULL',
b => ' `b` varchar(64) default NULL'
},
numeric_cols => [],
is_numeric => {},
engine => 'MyISAM',
type_for => { a => 'varchar', b => 'varchar' },
charset => 'latin1',
},
'Indexes with prefixes parse OK (fixes issue 1)'
);
is(
$tp->ansi_to_legacy( load_file('t/lib/samples/ansi.quoting.sql') ),
q{CREATE TABLE `t` (
`a` int(11) DEFAULT NULL,
`b``c` int(11) DEFAULT NULL,
`d"e` int(11) DEFAULT NULL,
`f
g` int(11) DEFAULT NULL,
`h\` int(11) DEFAULT NULL,
`i\"` int(11) DEFAULT NULL
)
},
'ANSI quotes (with all kinds of dumb things) get translated correctly'
);
$tbl = $tp->parse( load_file('t/lib/samples/sakila.film.sql') );
is_deeply(
$tbl,
{ cols => [
qw(film_id title description release_year language_id
original_language_id rental_duration rental_rate
length replacement_cost rating special_features
last_update)
],
non_generated_cols => [
qw(film_id title description release_year language_id
original_language_id rental_duration rental_rate
length replacement_cost rating special_features
last_update)
],
col_posn => {
film_id => 0,
title => 1,
description => 2,
release_year => 3,
language_id => 4,
original_language_id => 5,
rental_duration => 6,
rental_rate => 7,
length => 8,
replacement_cost => 9,
rating => 10,
special_features => 11,
last_update => 12,
},
is_autoinc => {
film_id => 1,
title => 0,
description => 0,
release_year => 0,
language_id => 0,
original_language_id => 0,
rental_duration => 0,
rental_rate => 0,
length => 0,
replacement_cost => 0,
rating => 0,
special_features => 0,
last_update => 0,
},
is_col => {
film_id => 1,
title => 1,
description => 1,
release_year => 1,
language_id => 1,
original_language_id => 1,
rental_duration => 1,
rental_rate => 1,
length => 1,
replacement_cost => 1,
rating => 1,
special_features => 1,
last_update => 1,
},
is_generated => {},
null_cols => [qw(description release_year original_language_id length rating special_features )],
is_nullable => {
description => 1,
release_year => 1,
original_language_id => 1,
length => 1,
special_features => 1,
rating => 1,
},
clustered_key => 'PRIMARY',
keys => {
PRIMARY => {
colnames => '`film_id`',
cols => [qw(film_id)],
col_prefixes => [undef],
is_col => { film_id => 1 },
is_nullable => 0,
is_unique => 1,
type => 'BTREE',
name => 'PRIMARY',
ddl => 'PRIMARY KEY (`film_id`),',
},
idx_title => {
colnames => '`title`',
cols => [qw(title)],
col_prefixes => [undef],
is_col => { title => 1, },
is_nullable => 0,
is_unique => 0,
type => 'BTREE',
name => 'idx_title',
ddl => 'KEY `idx_title` (`title`),',
},
idx_fk_language_id => {
colnames => '`language_id`',
cols => [qw(language_id)],
col_prefixes => [undef],
is_unique => 0,
is_col => { language_id => 1 },
is_nullable => 0,
type => 'BTREE',
name => 'idx_fk_language_id',
ddl => 'KEY `idx_fk_language_id` (`language_id`),',
},
idx_fk_original_language_id => {
colnames => '`original_language_id`',
cols => [qw(original_language_id)],
col_prefixes => [undef],
is_unique => 0,
is_col => { original_language_id => 1 },
is_nullable => 1,
type => 'BTREE',
name => 'idx_fk_original_language_id',
ddl => 'KEY `idx_fk_original_language_id` (`original_language_id`),',
},
},
defs => {
film_id => " `film_id` smallint(5) unsigned NOT NULL auto_increment",
title => " `title` varchar(255) NOT NULL",
description => " `description` text",
release_year => " `release_year` year(4) default NULL",
language_id => " `language_id` tinyint(3) unsigned NOT NULL",
original_language_id =>
" `original_language_id` tinyint(3) unsigned default NULL",
rental_duration =>
" `rental_duration` tinyint(3) unsigned NOT NULL default '3'",
rental_rate => " `rental_rate` decimal(4,2) NOT NULL default '4.99'",
length => " `length` smallint(5) unsigned default NULL",
replacement_cost => " `replacement_cost` decimal(5,2) NOT NULL default '19.99'",
rating => " `rating` enum('G','PG','PG-13','R','NC-17') default 'G'",
special_features =>
" `special_features` set('Trailers','Commentaries','Deleted Scenes','Behind the Scenes') default NULL",
last_update =>
" `last_update` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP",
},
numeric_cols => [
qw(film_id release_year language_id original_language_id rental_duration
rental_rate length replacement_cost)
],
is_numeric => {
film_id => 1,
release_year => 1,
language_id => 1,
original_language_id => 1,
rental_duration => 1,
rental_rate => 1,
length => 1,
replacement_cost => 1,
},
engine => 'InnoDB',
type_for => {
film_id => 'smallint',
title => 'varchar',
description => 'text',
release_year => 'year',
language_id => 'tinyint',
original_language_id => 'tinyint',
rental_duration => 'tinyint',
rental_rate => 'decimal',
length => 'smallint',
replacement_cost => 'decimal',
rating => 'enum',
special_features => 'set',
last_update => 'timestamp',
},
name => 'film',
charset => 'utf8',
},
'sakila.film',
);
is_deeply(
[$tp->sort_indexes($tbl)],
[qw(PRIMARY idx_fk_language_id idx_title idx_fk_original_language_id)],
'Sorted indexes OK'
);
is($tp->find_best_index($tbl), 'PRIMARY', 'Primary key is best');
is($tp->find_best_index($tbl, 'idx_title'), 'idx_title', 'Specified key is best');
throws_ok (
sub { $tp->find_best_index($tbl, 'foo') },
qr/does not exist/,
'Index does not exist',
);
$tbl = $tp->parse( load_file('t/lib/samples/temporary_table.sql') );
is_deeply(
$tbl,
{ cols => [qw(a)],
non_generated_cols => [qw(a)],
col_posn => { a => 0 },
is_col => { a => 1 },
is_autoinc => { a => 0 },
is_generated => {},
null_cols => [qw(a)],
is_nullable => { a => 1 },
clustered_key => undef,
keys => {},
defs => { a => ' `a` int(11) default NULL' },
numeric_cols => [qw(a)],
is_numeric => { a => 1 },
engine => 'MyISAM',
type_for => { a => 'int' },
name => 't',
charset => 'latin1',
},
'Temporary table',
);
my $want = { 'is_autoinc' => {
'sort_order' => 0,
'pfk-source_instrument_id' => 0,
'pfk-related_instrument_id' => 0
},
'null_cols' => [],
'numeric_cols' => [
'pfk-source_instrument_id', 'pfk-related_instrument_id',
'sort_order'
],
'cols' => [
'pfk-source_instrument_id', 'pfk-related_instrument_id',
'sort_order'
],
'non_generated_cols' => [
'pfk-source_instrument_id', 'pfk-related_instrument_id',
'sort_order'
],
is_autogenerated => {},
'col_posn' => {
'sort_order' => 2,
'pfk-source_instrument_id' => 0,
'pfk-related_instrument_id' => 1
},
clustered_key => 'PRIMARY',
'keys' => {
'sort_order' => {
'is_unique' => 0,
'is_col' => { 'sort_order' => 1 },
'name' => 'sort_order',
'type' => 'BTREE',
'col_prefixes' => [ undef ],
'is_nullable' => 0,
'colnames' => '`sort_order`',
'cols' => [ 'sort_order' ],
ddl => 'KEY `sort_order` (`sort_order`)',
},
'PRIMARY' => {
'is_unique' => 1,
'is_col' => {
'pfk-source_instrument_id' => 1,
'pfk-related_instrument_id' => 1
},
'name' => 'PRIMARY',
'type' => 'BTREE',
'col_prefixes' => [ undef, undef ],
'is_nullable' => 0,
'colnames' =>
'`pfk-source_instrument_id`,`pfk-related_instrument_id`',
'cols' =>
[ 'pfk-source_instrument_id', 'pfk-related_instrument_id' ],
ddl => 'PRIMARY KEY (`pfk-source_instrument_id`,`pfk-related_instrument_id`),',
}
},
'defs' => {
'sort_order' => ' `sort_order` int(11) NOT NULL',
'pfk-source_instrument_id' =>
' `pfk-source_instrument_id` int(10) unsigned NOT NULL',
'pfk-related_instrument_id' =>
' `pfk-related_instrument_id` int(10) unsigned NOT NULL'
},
'engine' => 'InnoDB',
'is_col' => {
'sort_order' => 1,
'pfk-source_instrument_id' => 1,
'pfk-related_instrument_id' => 1
},
'is_numeric' => {
'sort_order' => 1,
'pfk-source_instrument_id' => 1,
'pfk-related_instrument_id' => 1
},
'type_for' => {
'sort_order' => 'int',
'pfk-source_instrument_id' => 'int',
'pfk-related_instrument_id' => 'int'
},
'is_nullable' => {},
name => 'instrument_relation',
charset => 'latin1',
};
$tbl = $tp->parse( load_file('t/lib/samples/hyphentest.sql') );
is_deeply(
$tbl,
{ 'is_autoinc' => {
'sort_order' => 0,
'pfk-source_instrument_id' => 0,
'pfk-related_instrument_id' => 0
},
'null_cols' => [],
'numeric_cols' => [
'pfk-source_instrument_id', 'pfk-related_instrument_id',
'sort_order'
],
'cols' => [
'pfk-source_instrument_id', 'pfk-related_instrument_id',
'sort_order'
],
'non_generated_cols' => [
'pfk-source_instrument_id', 'pfk-related_instrument_id',
'sort_order'
],
is_generated => {},
'col_posn' => {
'sort_order' => 2,
'pfk-source_instrument_id' => 0,
'pfk-related_instrument_id' => 1
},
clustered_key => 'PRIMARY',
'keys' => {
'sort_order' => {
'is_unique' => 0,
'is_col' => { 'sort_order' => 1 },
'name' => 'sort_order',
'type' => 'BTREE',
'col_prefixes' => [ undef ],
'is_nullable' => 0,
'colnames' => '`sort_order`',
'cols' => [ 'sort_order' ],
ddl => 'KEY `sort_order` (`sort_order`)',
},
'PRIMARY' => {
'is_unique' => 1,
'is_col' => {
'pfk-source_instrument_id' => 1,
'pfk-related_instrument_id' => 1
},
'name' => 'PRIMARY',
'type' => 'BTREE',
'col_prefixes' => [ undef, undef ],
'is_nullable' => 0,
'colnames' =>
'`pfk-source_instrument_id`,`pfk-related_instrument_id`',
'cols' =>
[ 'pfk-source_instrument_id', 'pfk-related_instrument_id' ],
ddl => 'PRIMARY KEY (`pfk-source_instrument_id`,`pfk-related_instrument_id`),',
}
},
'defs' => {
'sort_order' => ' `sort_order` int(11) NOT NULL',
'pfk-source_instrument_id' =>
' `pfk-source_instrument_id` int(10) unsigned NOT NULL',
'pfk-related_instrument_id' =>
' `pfk-related_instrument_id` int(10) unsigned NOT NULL'
},
'engine' => 'InnoDB',
'is_col' => {
'sort_order' => 1,
'pfk-source_instrument_id' => 1,
'pfk-related_instrument_id' => 1
},
'is_numeric' => {
'sort_order' => 1,
'pfk-source_instrument_id' => 1,
'pfk-related_instrument_id' => 1
},
'type_for' => {
'sort_order' => 'int',
'pfk-source_instrument_id' => 'int',
'pfk-related_instrument_id' => 'int'
},
'is_nullable' => {},
name => 'instrument_relation',
charset => 'latin1',
},
'Hyphens in indexed columns',
);
$tbl = $tp->parse( load_file('t/lib/samples/ndb_table.sql') );
is_deeply(
$tbl,
{ cols => [qw(id)],
non_generated_cols => [qw(id)],
col_posn => { id => 0 },
is_col => { id => 1 },
is_autoinc => { id => 1 },
null_cols => [],
is_nullable => {},
clustered_key => undef,
keys => {
PRIMARY => {
cols => [qw(id)],
is_unique => 1,
is_col => { id => 1 },
name => 'PRIMARY',
type => 'BTREE',
col_prefixes => [undef],
is_nullable => 0,
colnames => '`id`',
ddl => 'PRIMARY KEY (`id`)',
}
},
defs => { id => ' `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT' },
numeric_cols => [qw(id)],
is_numeric => { id => 1 },
engine => 'ndbcluster',
type_for => { id => 'bigint' },
name => 'pipo',
charset => 'latin1',
is_generated => {},
},
'NDB table',
);
$tbl = $tp->parse( load_file('t/lib/samples/mixed-case.sql') );
is_deeply(
$tbl,
{ cols => [qw(a b mixedcol)],
non_generated_cols => [qw(a b mixedcol)],
col_posn => { a => 0, b => 1, mixedcol => 2 },
is_col => { a => 1, b => 1, mixedcol => 1 },
is_autoinc => { a => 0, b => 0, mixedcol => 0 },
is_generated => {},
null_cols => [qw(a b mixedcol)],
is_nullable => { a => 1, b => 1, mixedcol => 1 },
clustered_key => undef,
keys => {
mykey => {
colnames => '`a`,`b`,`mixedcol`',
cols => [qw(a b mixedcol)],
col_prefixes => [undef, undef, undef],
is_col => { a => 1, b => 1, mixedcol => 1 },
is_nullable => 3,
is_unique => 0,
type => 'BTREE',
name => 'mykey',
ddl => 'KEY `mykey` (`a`,`b`,`mixedcol`)',
},
},
defs => {
a => ' `a` int(11) default NULL',
b => ' `b` int(11) default NULL',
mixedcol => ' `mixedcol` int(11) default NULL',
},
numeric_cols => [qw(a b mixedcol)],
is_numeric => { a => 1, b => 1, mixedcol => 1 },
engine => 'MyISAM',
type_for => { a => 'int', b => 'int', mixedcol => 'int' },
name => 't',
charset => undef,
},
'Mixed-case identifiers',
);
$tbl = $tp->parse( load_file('t/lib/samples/one_key.sql') );
is_deeply(
$tbl,
{ cols => [qw(a b)],
non_generated_cols => [qw(a b)],
col_posn => { a => 0, b => 1 },
is_col => { a => 1, b => 1 },
is_autoinc => { a => 0, b => 0 },
null_cols => [qw(b)],
is_nullable => { b => 1 },
clustered_key => undef,
keys => {
PRIMARY => {
colnames => '`a`',
cols => [qw(a)],
col_prefixes => [undef],
is_col => { a => 1 },
is_nullable => 0,
is_unique => 1,
type => 'BTREE',
name => 'PRIMARY',
ddl => 'PRIMARY KEY (`a`)',
},
},
defs => {
a => ' `a` int(11) NOT NULL',
b => ' `b` char(50) default NULL',
},
numeric_cols => [qw(a)],
is_numeric => { a => 1 },
is_generated => {},
engine => 'MyISAM',
type_for => { a => 'int', b => 'char' },
name => 't2',
charset => 'latin1',
},
'No clustered key on MyISAM table'
);
# #############################################################################
# Test get_fks()
# #############################################################################
is_deeply(
$tp->get_fks( load_file('t/lib/samples/one_key.sql') ),
{},
'no fks'
);
is_deeply(
$tp->get_fks( load_file('t/lib/samples/one_fk.sql') ),
{
't1_ibfk_1' => {
name => 't1_ibfk_1',
colnames => '`a`',
cols => ['a'],
parent_tbl => { tbl => 't2' },
parent_tblname => '`t2`',
parent_colnames => '`a`',
parent_cols => ['a'],
ddl => 'CONSTRAINT `t1_ibfk_1` FOREIGN KEY (`a`) REFERENCES `t2` (`a`)',
},
},
'one fk'
);
is_deeply(
$tp->get_fks( load_file('t/lib/samples/one_fk.sql'), {database=>'foo'} ),
{
't1_ibfk_1' => {
name => 't1_ibfk_1',
colnames => '`a`',
cols => ['a'],
parent_tbl => { db => 'foo', tbl => 't2' },
parent_tblname => '`foo`.`t2`',
parent_cols => ['a'],
parent_colnames => '`a`',
ddl => 'CONSTRAINT `t1_ibfk_1` FOREIGN KEY (`a`) REFERENCES `t2` (`a`)',
},
},
'one fk with default database'
);
is_deeply(
$tp->get_fks( load_file('t/lib/samples/issue_331.sql') ),
{
'fk_1' => {
name => 'fk_1',
colnames => '`id`',
cols => ['id'],
parent_tbl => { tbl => 'issue_331_t1' },
parent_tblname => '`issue_331_t1`',
parent_colnames => '`t1_id`',
parent_cols => ['t1_id'],
ddl => 'CONSTRAINT `fk_1` FOREIGN KEY (`id`) REFERENCES `issue_331_t1` (`t1_id`)',
},
'fk_2' => {
name => 'fk_2',
colnames => '`id`',
cols => ['id'],
parent_tbl => { tbl => 'issue_331_t1' },
parent_tblname => '`issue_331_t1`',
parent_colnames => '`t1_id`',
parent_cols => ['t1_id'],
ddl => 'CONSTRAINT `fk_2` FOREIGN KEY (`id`) REFERENCES `issue_331_t1` (`t1_id`)',
}
},
'two fks (issue 331)'
);
# #############################################################################
# Sandbox test
# #############################################################################
SKIP: {
skip 'Cannot connect to sandbox master', 8 unless $dbh;
$sb->load_file('source', 't/lib/samples/check_table.sql');
# msandbox user does not have GRANT privs.
my $root_dbh = DBI->connect(
"DBI:mysql:host=127.0.0.1;port=12345;mysql_ssl=1", 'root', 'msandbox',
{ PrintError => 0, RaiseError => 1 });
$root_dbh->do(q[CREATE USER 'user'@'%' IDENTIFIED BY '';] ) || die($root_dbh->errstr);
$root_dbh->do(q[GRANT SELECT ON test.* TO 'user'@'%'] ) || die($root_dbh->errstr);
my $user_dbh = DBI->connect(
"DBI:mysql:host=127.0.0.1;port=12345", 'user', undef,
{ PrintError => 0, RaiseError => 1 });
ok(
$tp->check_table(
dbh => $dbh,
db => 'mysql',
tbl => 'db',
),
'Table exists'
);
ok(
!$tp->check_table(
dbh => $dbh,
db => 'mysql',
tbl => 'blahbleh',
),
'Table does not exist'
);
ok(
!$tp->check_table(
dbh => $user_dbh,
db => 'mysql',
tbl => 'db',
),
"Table exists but user can't see it"
);
ok(
!$tp->check_table(
dbh => $user_dbh,
db => 'mysql',
tbl => 'blahbleh',
),
"Table does not exist and user can't see it"
);
ok(
$tp->check_table(
dbh => $dbh,
db => 'test',
tbl => 't_',
),
'Table t_ exists'
);
ok(
$tp->check_table(
dbh => $dbh,
db => 'test',
tbl => 't%_',
),
'Table t%_ exists'
);
$user_dbh->disconnect();
$root_dbh->do("DROP USER 'user'\@'\%'");
$root_dbh->disconnect();
};
SKIP: {
skip 'Sandbox master does not have the sakila database', 2
unless $dbh && @{$dbh->selectcol_arrayref("SHOW DATABASES LIKE 'sakila'")};
is_deeply(
[$tp->find_possible_keys(
$dbh, 'sakila', 'film_actor', $q, 'film_id > 990 and actor_id > 1')],
[qw(idx_fk_film_id PRIMARY)],
'Best index for WHERE clause'
);
is_deeply(
[$tp->find_possible_keys(
$dbh, 'sakila', 'film_actor', $q, 'film_id > 990 or actor_id > 1')],
[qw(idx_fk_film_id PRIMARY)],
'Best index for WHERE clause with sort_union'
);
};
# #############################################################################
# Issue 109: Test schema changes in 5.1
# #############################################################################
sub cmp_ddls {
my ( $desc, $v1, $v2 ) = @_;
$tbl = $tp->parse( load_file($v1) );
my $tbl2 = $tp->parse( load_file($v2) );
# The defs for each will differ due to string case: 'default' vs. 'DEFAULT'.
# Everything else should be identical, though. So we'll chop out the defs,
# compare them later, and check the rest first.
my %defs = %{$tbl->{defs}};
my %defs2 = %{$tbl2->{defs}};
$tbl->{defs} = ();
$tbl2->{defs} = ();
is_deeply($tbl, $tbl2, "$desc SHOW CREATE parse identically");
my $defstr = '';
my $defstr2 = '';
foreach my $col ( keys %defs ) {
$defstr .= lc $defs{$col};
$defstr2 .= lc $defs2{$col};
}
is($defstr, $defstr2, "$desc defs are identical (except for case)");
return;
}
cmp_ddls('v5.0 vs. v5.1', 't/lib/samples/issue_109-01-v50.sql', 't/lib/samples/issue_109-01-v51.sql');
# #############################################################################
# Issue 132: mk-parallel-dump halts with error when enum contains backtick
# #############################################################################
$tbl = $tp->parse( load_file('t/lib/samples/issue_132.sql') );
is_deeply(
$tbl,
{ cols => [qw(country)],
non_generated_cols => [qw(country)],
col_posn => { country => 0 },
is_col => { country => 1 },
is_autoinc => { country => 0 },
is_generated => {},
null_cols => [qw(country)],
is_nullable => { country => 1 },
clustered_key => undef,
keys => {},
defs => { country => " `country` enum('','Cote D`ivoire') default NULL"},
numeric_cols => [],
is_numeric => {},
engine => 'MyISAM',
type_for => { country => 'enum' },
name => 'issue_132',
charset => 'latin1',
},
'ENUM col with backtick in value (issue 132)'
);
# #############################################################################
# issue 328: remove AUTO_INCREMENT from schema for checksumming.
# #############################################################################
my $schema1 = load_file('t/lib/samples/auto-increment-actor.sql');
my $schema2 = load_file('t/lib/samples/no-auto-increment-actor.sql');
is(
$tp->remove_auto_increment($schema1),
$schema2,
'AUTO_INCREMENT is gone',
);
# #############################################################################
# Issue 330: mk-parallel-dump halts with error when comments contain pairing `
# #############################################################################
$tbl = $tp->parse( load_file('t/lib/samples/issue_330_backtick_pair_in_col_comments.sql') );
is_deeply(
$tbl,
{ cols => [qw(a)],
non_generated_cols => [qw(a)],
col_posn => { a => 0 },
is_col => { a => 1 },
is_generated => {},
is_autoinc => { a => 0 },
null_cols => [qw(a)],
is_nullable => { a => 1 },
clustered_key => undef,
keys => {},
defs => { a => " `a` int(11) DEFAULT NULL COMMENT 'issue_330 `alex`'" },
numeric_cols => [qw(a)],
is_numeric => { a => 1 },
engine => 'MyISAM',
type_for => { a => 'int' },
name => 'issue_330',
charset => 'latin1',
},
'issue with pairing backticks in column comments (issue 330)'
);
$tbl = $tp->parse( load_file('t/lib/samples/issue_pt-193_backtick_in_col_comments.sql') );
is_deeply(
$tbl,
{ cols => [qw(id f22abcde f23abc)],
non_generated_cols => [qw(id f22abcde f23abc)],
col_posn => { id => 0, f22abcde => 1, f23abc => 2 },
is_col => { id => 1, f22abcde => 1, f23abc => 1 },
is_autoinc => { id => 1, f22abcde => 0, f23abc => 0 },
is_generated => {},
null_cols => [qw(f22abcde)],
is_nullable => { f22abcde => 1},
clustered_key => undef,
keys => {},
defs => { id => " `id` int(11) NOT NULL AUTO_INCREMENT",
"f22abcde" => " `f22abcde` int(10) unsigned DEFAULT NULL COMMENT 'xxx`XXx'",
"f23abc" => " `f23abc` int(10) unsigned NOT NULL DEFAULT '255' COMMENT \"`yyy\""
},
numeric_cols => [qw(id f22abcde f23abc)],
is_numeric => { id => 1, f22abcde => 1, f23abc => 1 },
engine => 'InnoDB',
type_for => { id => 'int', f22abcde => 'int', f23abc => 'int' },
name => 't3',
charset => 'latin1',
},
'issue with pairing backticks in column comments (issue 330)'
);
# #############################################################################
# Issue 170: mk-parallel-dump dies when table-status Data_length is NULL
# #############################################################################
# The underlying problem for issue 170 is that MySQLDump doesn't eval some
# of its queries so when MySQLFind uses it and hits a broken table it dies.
eval {
$tp->parse(undef);
};
is(
$EVAL_ERROR,
'',
'No error parsing undef ddl'
);
# #############################################################################
# Issue 295: Enhance rules for clustered keys in mk-duplicate-key-checker
# #############################################################################
# Make sure get_keys() gets a clustered index that's not the primary key.
my $ddl = load_file('t/lib/samples/non_pk_ck.sql');
my (undef, $ck) = $tp->get_keys($ddl, {}, {i=>0,j=>1});
is(
$ck,
'i_idx',
'Get first unique, non-nullable index as clustered key'
);
# #############################################################################
# Issue 388: mk-table-checksum crashes when column with comma in the
# name is used in a key
# #############################################################################
$tbl = $tp->parse( load_file("$sample/issue-388.sql") );
is_deeply(
$tbl,
{
clustered_key => undef,
col_posn => { 'first, last' => 1, id => 0 },
cols => [ 'id', 'first, last' ],
non_generated_cols => [ 'id', 'first, last' ],
defs => {
'first, last' => ' `first, last` varchar(32) default NULL',
id => ' `id` int(11) NOT NULL auto_increment',
},
engine => 'MyISAM',
is_autoinc => { 'first, last' => 0, id => 1 },
is_generated => {},
is_col => { 'first, last' => 1, id => 1 },
is_nullable => { 'first, last' => 1 },
is_numeric => { id => 1 },
name => 'foo',
null_cols => [ 'first, last' ],
numeric_cols => [ 'id' ],
type_for => {
'first, last' => 'varchar',
id => 'int',
},
keys => {
PRIMARY => {
col_prefixes => [ undef ],
colnames => '`id`',
cols => [ 'id' ],
ddl => 'PRIMARY KEY (`id`),',
is_col => { id => 1 },
is_nullable => 0,
is_unique => 1,
name => 'PRIMARY',
type => 'BTREE',
},
nameindex => {
col_prefixes => [ undef ],
colnames => '`first, last`',
cols => [ 'first, last' ],
ddl => 'KEY `nameindex` (`first, last`)',
is_col => { 'first, last' => 1 },
is_nullable => 1,
is_unique => 0,
name => 'nameindex',
type => 'BTREE',
},
},
charset => undef,
},
'Index with comma in its name (issue 388)'
);
# #############################################################################
# Bug 1047335: pt-duplicate-key-checker fails when it encounters a crashed table
# https://bugs.launchpad.net/percona-toolkit/+bug/1047335
# #############################################################################
SKIP: {
skip "No /dev/urandom, can't corrupt the database", 2 unless -e q{/dev/urandom};
skip "Cannot corrupt a table in MySQL 8", 2 if ($sandbox_version gt '5.7');
# We need to create a new server here, otherwise the whole test suite might die
# if the crashed table can't be dropped.
my $source3_port = 2900;
my $source_basedir = "/tmp/$source3_port";
diag(`$trunk/sandbox/stop-sandbox $source3_port >/dev/null`);
diag(`$trunk/sandbox/start-sandbox source $source3_port >/dev/null`);
my $dbh3 = $sb->get_dbh_for("source3");
$sb->load_file('source3', "t/lib/samples/bug_1047335_crashed_table.sql");
my $db_dir = "$source_basedir/data/bug_1047335";
my $myi = glob("$db_dir/crashed_table.[Mm][Yy][Iy]");
my $frm = glob("$db_dir/crashed_table.[Ff][Rr][Mm]");
die "Cannot find .myi file for crashed_table" unless $myi && -f $myi;
# Truncate the .myi file to corrupt it
truncate($myi, 4096);
# Corrupt the .frm file
open my $urand_fh, q{<}, "/dev/urandom"
or die "Cannot open /dev/urandom: $OS_ERROR";
open my $tmp_fh, q{>}, $frm
or die "Cannot open $frm: $OS_ERROR";
print { $tmp_fh } scalar(<$urand_fh>), slurp_file($frm), scalar(<$urand_fh>);
close $tmp_fh;
close $urand_fh;
$dbh3->do("FLUSH TABLES");
eval { $dbh3->do("SELECT etc FROM bug_1047335.crashed_table WHERE etc LIKE '10001' ORDER BY id ASC LIMIT 1") };
eval { $tp->get_create_table($dbh3, 'bug_1047335', 'crashed_table') };
ok(
$EVAL_ERROR,
"get_create_table dies if SHOW CREATE TABLE failed",
);
# This might fail. Doesn't matter -- stop_sandbox will just rm -rf the folder
eval { $dbh3->do("DROP DATABASE IF EXISTS bug_1047335") };
$dbh3->do(q{DROP DATABASE IF EXISTS bug_1047335_2});
$dbh3->do(q{CREATE DATABASE bug_1047335_2});
my $broken_frm = "$trunk/t/lib/samples/broken_tbl.frm";
my $db_dir_2 = "$source_basedir/data/bug_1047335_2";
diag(`cp $broken_frm $db_dir_2 2>&1`);
$dbh3->do("FLUSH TABLES");
eval { $tp->get_create_table($dbh3, 'bug_1047335_2', 'broken_tbl') };
ok(
$EVAL_ERROR,
"get_create_table dies if SHOW CREATE TABLE failed (using broken_tbl.frm)",
);
diag(`$trunk/sandbox/stop-sandbox $source3_port >/dev/null`);
}
# #############################################################################
# pt-duplicate-key-checker doesn't support triple quote in column name
# https://bugs.launchpad.net/percona-toolkit/+bug/1462904
# #############################################################################
$tbl = $tp->parse(load_file('t/lib/samples/triple-quoted-col.sql'));
is_deeply(
$tbl,
{
clustered_key => undef,
col_posn => { 'foo' => 0, bar => 1 },
is_generated => {},
cols => [ 'foo', 'bar' ],
non_generated_cols => [ 'foo', 'bar' ],
defs => {
'foo' => ' `foo` int(11) DEFAULT NULL',
'bar' => ' ```bar``` int(11) DEFAULT NULL',
},
engine => 'InnoDB',
is_autoinc => { foo => 0, bar => 0 },
is_col => { foo => 1, bar => 1 },
is_nullable => { foo => 1, bar => 1 },
is_numeric => { foo => 1, bar => 1 },
name => 't',
null_cols => [ 'foo', 'bar' ],
numeric_cols => [ 'foo', 'bar' ],
type_for => {
foo => 'int',
bar => 'int',
},
keys => {},
charset => undef,
},
'Literal backticks (bug 1462904)'
);
SKIP: {
skip "generated column tests require MySQL 5.7+", 1
if $sandbox_version lt '5.7';
$tbl = $tp->parse( load_file('t/lib/samples/generated_cols.sql') );
is_deeply(
$tbl,
{
charset => 'utf8mb4',
clustered_key => 'PRIMARY',
col_posn => { column2 => 1, column3 => 2, id => 0 },
cols => [ 'id', 'column2', 'column3' ],
non_generated_cols => [ 'id', 'column2' ],
defs => {
column2 => ' `column2` int(11) DEFAULT NULL',
column3 => ' `column3` int(11) GENERATED ALWAYS AS ((`column2` + 1)) STORED',
id => ' `id` int(11) NOT NULL'
},
engine => 'InnoDB',
is_autoinc => { column2 => 0, column3 => 0, id => 0 },
is_col => { column2 => 1, id => 1 },
is_generated => { column3 => 1 },
is_nullable => { column2 => 1, column3 => 1 },
is_numeric => { column2 => 1, column3 => 1, id => 1 },
keys => {
PRIMARY => {
col_prefixes => [ undef ],
colnames => '`id`',
cols => [ 'id' ],
ddl => 'PRIMARY KEY (`id`)',
is_col => { id => 1 },
is_nullable => 0,
is_unique => 1,
name => 'PRIMARY',
type => 'BTREE'
}
},
name => 't1',
null_cols => [ 'column2', 'column3' ],
numeric_cols => [ 'id', 'column2', 'column3' ],
type_for => { column2 => 'int', column3 => 'int', id => 'int' }
},
'Generated columns is OK',
) or die Data::Dumper::Dumper($tbl);
}
# Test that the GENERATED word in a column comment doesn't make that column
# to be detected as a MySQL 5.7+ generated column.
$tbl = $tp->parse( load_file('t/lib/samples/generated_cols_comments.sql') );
is_deeply(
$tbl,
{
charset => 'latin1',
clustered_key => 'PRIMARY',
col_posn => {
id => 0,
source => 1,
tso_id => 2
},
cols => [
'id',
'source',
'tso_id'
],
defs => {
id => ' `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT \'The unique id of the audit record.\'',
source => ' `source` enum(\'val1\',\'val2\') NOT NULL COMMENT \'Transaction originator\'',
tso_id => ' `tso_id` int(11) unsigned NOT NULL DEFAULT \'0\' COMMENT \'An internally generated transaction.\''
},
engine => 'InnoDB',
is_autoinc => {
id => 1,
source => 0,
tso_id => 0
},
is_col => {
id => 1,
source => 1,
tso_id => 1
},
is_generated => {},
is_nullable => {},
is_numeric => {
id => 1,
tso_id => 1
},
keys => {
PRIMARY => {
col_prefixes => [
undef
],
colnames => '`id`',
cols => [
'id'
],
ddl => 'PRIMARY KEY (`id`),',
is_col => {
id => 1
},
is_nullable => 0,
is_unique => 1,
name => 'PRIMARY',
type => 'BTREE'
}
},
name => 't1',
non_generated_cols => [
'id',
'source',
'tso_id'
],
null_cols => [],
numeric_cols => [
'id',
'tso_id'
],
type_for => {
id => 'int',
source => 'enum',
tso_id => 'int'
}
},
'Column having the word "generated" as part of the comment is OK',
) or diag Data::Dumper::Dumper($tbl);
$tbl = $tp->parse( load_file('t/lib/samples/generated_cols_comments_2.sql') );
is_deeply(
$tbl,
{
charset => 'latin1',
clustered_key => undef,
col_posn => {
c => 1,
id => 0,
v => 2
},
cols => [
'id',
'c',
'v'
],
defs => {
c => ' `c` varchar(100) NOT NULL DEFAULT \'\' COMMENT \'Generated\'',
id => ' `id` int(11) NOT NULL',
v => ' `v` int(11) DEFAULT NULL'
},
engine => 'InnoDB',
is_autoinc => {
c => 0,
id => 0,
v => 0
},
is_col => {
c => 1,
id => 1,
v => 1
},
is_generated => {},
is_nullable => {
v => 1
},
is_numeric => {
id => 1,
v => 1
},
keys => {},
name => 't',
non_generated_cols => [
'id',
'c',
'v'
],
null_cols => [
'v'
],
numeric_cols => [
'id',
'v'
],
type_for => {
c => 'varchar',
id => 'int',
v => 'int'
}
},
'Column having the word "generated" as part of the comment is OK',
) or diag Data::Dumper::Dumper($tbl);
# #############################################################################
# Done.
# #############################################################################
$sb->wipe_clean($dbh) if $dbh;
ok($sb->ok(), "Sandbox servers") or BAIL_OUT(__FILE__ . " broke the sandbox");
done_testing;
exit;