#!/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 KeySize; use TableParser; use Quoter; use DSNParser; 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('master'); if ( !$dbh ) { plan skip_all => "Cannot connect to sandbox master"; } my $q = new Quoter(); my $tp = new TableParser(Quoter => $q); my $ks = new KeySize(Quoter=>$q); my $tbl; my $struct; my %key; my ($size, $chosen_key); sub key_info { my ( $file, $db, $tbl, $key, $cols ) = @_; $sb->load_file('master', $file, $db); my $tbl_name = $q->quote($db, $tbl); my $struct = $tp->parse( load_file($file) ); return ( name => $key, cols => $cols || $struct->{keys}->{$key}->{cols}, tbl_name => $tbl_name, tbl_struct => $struct, dbh => $dbh, ); } $sb->create_dbs($dbh, ['test']); isa_ok($ks, 'KeySize'); # With an empty table, the WHERE is impossible, so MySQL should optimize # away the query, and key_len and rows will be NULL in EXPLAIN. %key = key_info('t/lib/samples/dupe_key.sql', 'test', 'dupe_key', 'a'); is( $ks->get_key_size(%key), undef, 'Empty table, impossible where' ); # Populate the table to make the WHERE possible. $dbh->do('INSERT INTO test.dupe_key VALUE (1,2,3),(4,5,6),(7,8,9),(0,0,0)'); is_deeply( [$ks->get_key_size(%key)], [20, 'a'], 'Single column int key' ); $key{name} = 'a_2'; is_deeply( [$ks->get_key_size(%key)], [40, 'a_2'], 'Two column int key' ); $sb->load_file('master', 't/lib/samples/issue_331-parent.sql', 'test'); %key = key_info('t/lib/samples/issue_331.sql', 'test', 'issue_331_t2', 'fk_1', ['id']); ($size, $chosen_key) = $ks->get_key_size(%key); is( $size, 8, 'Foreign key size' ); is( $chosen_key, 'PRIMARY', 'PRIMARY key chosen for foreign key' ); # ############################################################################# # Issue 364: Argument "9,8" isn't numeric in multiplication (*) at # mk-duplicate-key-checker line 1894 # ############################################################################# $dbh->do('USE test'); $dbh->do('DROP TABLE IF EXISTS test.issue_364'); %key = key_info( 't/lib/samples/issue_364.sql', 'test', 'issue_364', 'BASE_KID_ID', [qw(BASE_KID_ID ID)] ); $sb->load_file('master', 't/lib/samples/issue_364-data.sql', 'test'); # This issue had another issue: the key is ALL CAPS, but TableParser # lowercases all identifies, so KeySize said the key didn't exist. # This was the root problem. Once KeySize saw the key it added a # FORCE INDEX and the index_merge went away. Later, we'll drop the # real key and add one back over the same columns so that KeySize # won't see its key but one will exist with which to do merge_index. ok( $ks->_key_exists(%key), 'Key exists (issue 364)' ); my $output = `/tmp/12345/use -D test -e 'EXPLAIN SELECT BASE_KID_ID, ID FROM test.issue_364 WHERE BASE_KID_ID=1 OR ID=1'`; like( $output, qr/index_merge/, 'Query uses index_merge (issue 364)' ); # lets help get_key_size get an accurate figure by ANALYZING the table first # otherwise we get spurious false positives that are off by just a bit $dbh->do('ANALYZE TABLE test.issue_364'); ($size, $chosen_key) = $ks->get_key_size(%key); is( $size, 17 * 176, 'Key size (issue 364)' ); is( $chosen_key, 'BASE_KID_ID', 'Chosen key (issue 364)' ); is( $ks->error(), '', 'No error (issue 364)' ); my $want = $sandbox_version lt '5.7' ? qr/^extra: Using where; Using index id: 1 key: BASE_KID_ID key_len: 17 possible_keys: BASE_KID_ID ref: NULL rows: 17[1-9] select_type: SIMPLE table: issue_364 type: index\Z/ : qr/^extra: Using where; Using index filtered: 1.13 id: 1 key: BASE_KID_ID key_len: 17 partitions: NULL possible_keys: BASE_KID_ID ref: NULL rows: 176 select_type: SIMPLE table: issue_364 type: index/; like( $ks->explain(), $want, 'EXPLAIN plan (issue 364)' ); is( $ks->query(), 'EXPLAIN SELECT BASE_KID_ID, ID FROM `test`.`issue_364` FORCE INDEX (`BASE_KID_ID`) WHERE BASE_KID_ID=1 OR ID=1', 'Query (issue 364)' ); # KeySize doesn't actually check the table to see if the key exists. # It trusts that tbl_struct->{keys} is accurate. So if we delete the # key here, we'll fool KeySize and simulate the original problem. delete $key{tbl_struct}->{keys}->{'base_kid_id'}; ($size, $chosen_key) = $ks->get_key_size(%key); is( $size, undef, 'Key size 0 (issue 364)' ); is( $chosen_key, undef, 'Chose multiple keys (issue 364)' ); is( $ks->error(), 'MySQL chose multiple keys: BASE_KID_ID,PRIMARY', 'Error about multiple keys (issue 364)' ); is( $ks->query(), 'EXPLAIN SELECT BASE_KID_ID, ID FROM `test`.`issue_364` WHERE BASE_KID_ID=1 OR ID=1', 'Query without FORCE INDEX (issue 364)' ); # ############################################################################# # https://bugs.launchpad.net/percona-toolkit/+bug/1201443 # ############################################################################# $sb->load_file('master', "t/pt-duplicate-key-checker/samples/fk_chosen_index_bug_1201443.sql"); ($size, $chosen_key) = $ks->get_key_size( name => 'child_ibfk_2', cols => [qw(parent_id)], tbl_name => 'fk_chosen_index_bug_1201443.child', tbl_struct => { charset => 'latin1', clustered_key => undef, col_posn => { id => 0, parent_id => 1 }, cols => [ 'id', 'parent_id' ], defs => { id => ' `id` int(11) NOT NULL AUTO_INCREMENT', parent_id => ' `parent_id` int(11) NOT NULL' }, engine => 'InnoDB', is_autoinc => { id => 1, parent_id => 0 }, is_col => { id => 1, parent_id => 1 }, is_nullable => {}, is_numeric => { id => 1, parent_id => 1 }, keys => { id => { col_prefixes => [ undef ], colnames => '`id`', cols => [ 'id' ], ddl => 'KEY `id` (`id`),', is_col => { id => 1 }, is_nullable => 0, is_unique => 0, name => 'id', type => 'BTREE' }, parent_id => { col_prefixes => [ undef ], colnames => '`parent_id`', cols => [ 'parent_id' ], ddl => 'KEY `parent_id` (`parent_id`),', is_col => { parent_id => 1 }, is_nullable => 0, is_unique => 0, name => 'parent_id', type => 'BTREE' } }, name => 'child', null_cols => [], numeric_cols => [ 'id', 'parent_id' ], type_for => { id => 'int', parent_id => 'int' } }, dbh => $dbh, ); cmp_ok( $size, '>', 15_000, # estimages range from 15k to 30k "Bug 1201443: size" ); is( $chosen_key, 'parent_id', "Bug 1201443: chosen key" ); # ############################################################################# # Done. # ############################################################################# $output = ''; { local *STDERR; open STDERR, '>', \$output; $ks->_d('Complete test coverage'); } like( $output, qr/Complete test coverage/, '_d() works' ); $sb->wipe_clean($dbh); ok($sb->ok(), "Sandbox servers") or BAIL_OUT(__FILE__ . " broke the sandbox"); done_testing;