#!/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 Test::More; use English qw(-no_match_vars); use QueryRewriter; use QueryParser; use PerconaTest; my $qp = new QueryParser; my $qr = new QueryRewriter( QueryParser => $qp ); isa_ok( $qp, 'QueryParser' ); # A subroutine to make tests easy to write. sub test_query { my ( $query, $aliases, $tables, $msg, %args ) = @_; is_deeply( $qp->get_aliases( $query, $args{list} ), $aliases, "get_aliases: $msg", ); is_deeply( [ $qp->get_tables($query) ], $tables, "get_tables: $msg", ); return; } # ############################################################################# # Misc stuff. # ############################################################################# is( $qp->trim_identifier('`foo` '), 'foo', 'Trim backticks and spaces' ); is( $qp->trim_identifier(' `db`.`t1`'), 'db.t1', 'Trim more backticks and spaces' ); # ############################################################################# # All manner of "normal" SELECT queries. # ############################################################################# # 1 table test_query( 'SELECT * FROM t1 WHERE id = 1', { DATABASE => {}, TABLE => { 't1' => 't1', }, }, [qw(t1)], 'one table no alias' ); test_query( 'SELECT * FROM t1 a WHERE id = 1', { DATABASE => {}, TABLE => { 'a' => 't1', }, }, [qw(t1)], 'one table implicit alias' ); test_query( 'SELECT * FROM t1 AS a WHERE id = 1', { DATABASE => {}, TABLE => { 'a' => 't1', } }, [qw(t1)], 'one table AS alias' ); test_query( 'SELECT * FROM t1', { DATABASE => {}, TABLE => { t1 => 't1', } }, [qw(t1)], 'one table no alias and no following clauses', ); # 2 tables test_query( 'SELECT * FROM t1, t2 WHERE id = 1', { DATABASE => {}, TABLE => { 't1' => 't1', 't2' => 't2', }, }, [qw(t1 t2)], 'two tables no aliases' ); test_query( 'SELECT * FROM t1 a, t2 WHERE foo = "bar"', { DATABASE => {}, TABLE => { a => 't1', t2 => 't2', }, }, [qw(t1 t2)], 'two tables implicit alias and no alias', ); test_query( 'SELECT * FROM t1 a, t2 b WHERE id = 1', { DATABASE => {}, TABLE => { 'a' => 't1', 'b' => 't2', }, }, [qw(t1 t2)], 'two tables implicit aliases' ); test_query( 'SELECT * FROM t1 AS a, t2 AS b WHERE id = 1', { DATABASE => {}, TABLE => { 'a' => 't1', 'b' => 't2', }, }, [qw(t1 t2)], 'two tables AS aliases' ); test_query( 'SELECT * FROM t1 AS a, t2 b WHERE id = 1', { DATABASE => {}, TABLE => { 'a' => 't1', 'b' => 't2', }, }, [qw(t1 t2)], 'two tables AS alias and implicit alias' ); test_query( 'SELECT * FROM t1 a, t2 AS b WHERE id = 1', { DATABASE => {}, TABLE => { 'a' => 't1', 'b' => 't2', }, }, [qw(t1 t2)], 'two tables implicit alias and AS alias' ); test_query( 'SELECT * FROM t1 a, t2 AS b WHERE id = 1', [ 't1 a', 't2 AS b', ], [qw(t1 t2)], 'two tables implicit alias and AS alias, with alias', list => 1, ); # ANSI JOINs test_query( 'SELECT * FROM t1 JOIN t2 ON a.id = b.id', { DATABASE => {}, TABLE => { 't1' => 't1', 't2' => 't2', }, }, [qw(t1 t2)], 'two tables no aliases JOIN' ); test_query( 'SELECT * FROM t1 a JOIN t2 b ON a.id = b.id', { DATABASE => {}, TABLE => { 'a' => 't1', 'b' => 't2', }, }, [qw(t1 t2)], 'two tables implicit aliases JOIN' ); test_query( 'SELECT * FROM t1 AS a JOIN t2 as b ON a.id = b.id', { DATABASE => {}, TABLE => { 'a' => 't1', 'b' => 't2', }, }, [qw(t1 t2)], 'two tables AS aliases JOIN' ); test_query( 'SELECT * FROM t1 AS a JOIN t2 b ON a.id=b.id WHERE id = 1', { DATABASE => {}, TABLE => { a => 't1', b => 't2', }, }, [qw(t1 t2)], 'two tables AS alias and implicit alias JOIN' ); test_query( 'SELECT * FROM t1 LEFT JOIN t2 ON a.id = b.id', { DATABASE => {}, TABLE => { 't1' => 't1', 't2' => 't2', }, }, [qw(t1 t2)], 'two tables no aliases LEFT JOIN' ); test_query( 'SELECT * FROM t1 a LEFT JOIN t2 b ON a.id = b.id', { DATABASE => {}, TABLE => { 'a' => 't1', 'b' => 't2', }, }, [qw(t1 t2)], 'two tables implicit aliases LEFT JOIN' ); test_query( 'SELECT * FROM t1 AS a LEFT JOIN t2 as b ON a.id = b.id', { DATABASE => {}, TABLE => { 'a' => 't1', 'b' => 't2', }, }, [qw(t1 t2)], 'two tables AS aliases LEFT JOIN' ); test_query( 'SELECT * FROM t1 AS a LEFT JOIN t2 b ON a.id=b.id WHERE id = 1', { DATABASE => {}, TABLE => { a => 't1', b => 't2', }, }, [qw(t1 t2)], 'two tables AS alias and implicit alias LEFT JOIN' ); # 3 tables test_query( 'SELECT * FROM t1 JOIN t2 ON t1.col1=t2.col2 JOIN t3 ON t2.col3 = t3.col4 WHERE foo = "bar"', { DATABASE => {}, TABLE => { t1 => 't1', t2 => 't2', t3 => 't3', }, }, [qw(t1 t2 t3)], 'three tables no aliases JOIN' ); test_query( 'SELECT * FROM t1 AS a, t2, t3 c WHERE id = 1', { DATABASE => {}, TABLE => { a => 't1', t2 => 't2', c => 't3', }, }, [qw(t1 t2 t3)], 'three tables AS alias, no alias, implicit alias' ); test_query( 'SELECT * FROM t1 a, t2 b, t3 c WHERE id = 1', { DATABASE => {}, TABLE => { a => 't1', b => 't2', c => 't3', }, }, [qw(t1 t2 t3)], 'three tables implicit aliases' ); # Db-qualified tables test_query( 'SELECT * FROM db.t1 AS a WHERE id = 1', { TABLE => { 'a' => 't1', }, 'DATABASE' => { 't1' => 'db', }, }, [qw(db.t1)], 'one db-qualified table AS alias' ); test_query( 'SELECT * FROM `db`.`t1` AS a WHERE id = 1', { TABLE => { 'a' => 't1', }, 'DATABASE' => { 't1' => 'db', }, }, [qw(`db`.`t1`)], 'one db-qualified table AS alias with backticks' ); # Other cases test_query( q{SELECT a FROM store_orders_line_items JOIN store_orders}, { DATABASE => {}, TABLE => { store_orders_line_items => 'store_orders_line_items', store_orders => 'store_orders', }, }, [qw(store_orders_line_items store_orders)], 'Embedded ORDER keyword', ); # ############################################################################# # Non-SELECT queries. # ############################################################################# test_query( 'UPDATE foo AS bar SET value = 1 WHERE 1', { DATABASE => {}, TABLE => { bar => 'foo', }, }, [qw(foo)], 'update with one AS alias', ); test_query( 'UPDATE IGNORE foo bar SET value = 1 WHERE 1', { DATABASE => {}, TABLE => { bar => 'foo', }, }, [qw(foo)], 'update ignore with one implicit alias', ); test_query( 'UPDATE IGNORE bar SET value = 1 WHERE 1', { DATABASE => {}, TABLE => { bar => 'bar', }, }, [qw(bar)], 'update ignore with one not aliased', ); test_query( 'UPDATE LOW_PRIORITY baz SET value = 1 WHERE 1', { DATABASE => {}, TABLE => { baz => 'baz', }, }, [qw(baz)], 'update low_priority with one not aliased', ); test_query( 'UPDATE LOW_PRIORITY IGNORE bat SET value = 1 WHERE 1', { DATABASE => {}, TABLE => { bat => 'bat', }, }, [qw(bat)], 'update low_priority ignore with one not aliased', ); test_query( 'INSERT INTO foo VALUES (1)', { DATABASE => {}, TABLE => { foo => 'foo', } }, [qw(foo)], 'insert with one not aliased', ); test_query( 'INSERT INTO foo VALUES (1) ON DUPLICATE KEY UPDATE bar = 1', { DATABASE => {}, TABLE => { foo => 'foo', }, }, [qw(foo)], 'insert / on duplicate key update', ); # ############################################################################# # Non-DMS queries. # ############################################################################# test_query( 'BEGIN', { DATABASE => {}, TABLE => {}, }, [], 'BEGIN' ); # ############################################################################# # Diabolical dbs and tbls with spaces in their names. # ############################################################################# test_query( 'select * from `my table` limit 1;', { DATABASE => {}, TABLE => { 'my table' => 'my table', } }, ['`my table`'], 'one table with space in name, not aliased', ); test_query( 'select * from `my database`.mytable limit 1;', { TABLE => { mytable => 'mytable', }, DATABASE => { mytable => 'my database', }, }, ['`my database`.mytable'], 'one db.tbl with space in db, not aliased', ); test_query( 'select * from `my database`.`my table` limit 1; ', { TABLE => { 'my table' => 'my table', }, DATABASE => { 'my table' => 'my database', }, }, ['`my database`.`my table`'], 'one db.tbl with space in both db and tbl, not aliased', ); # ############################################################################# # Issue 185: QueryParser fails to parse table ref for a JOIN ... USING # ############################################################################# test_query( 'select n.column1 = a.column1, n.word3 = a.word3 from db2.tuningdetail_21_265507 n inner join db1.gonzo a using(gonzo)', { TABLE => { 'n' => 'tuningdetail_21_265507', 'a' => 'gonzo', }, 'DATABASE' => { 'tuningdetail_21_265507' => 'db2', 'gonzo' => 'db1', }, }, [qw(db2.tuningdetail_21_265507 db1.gonzo)], 'SELECT with JOIN ON and no WHERE (issue 185)' ); # ############################################################################# test_query( 'select 12_13_foo from (select 12foo from 123_bar) as 123baz', { DATABASE => {}, TABLE => { '123baz' => undef, }, }, [qw(123_bar)], 'Subquery in the FROM clause' ); test_query( q{UPDATE GARDEN_CLUPL PL, GARDENJOB GC, APLTRACT_GARDENPLANT ABU SET } . q{GC.MATCHING_POT = 5, GC.LAST_GARDENPOT = 5, GC.LAST_NAME=} . q{'Rotary', GC.LAST_BUCKET='Pail', GC.LAST_UPDATE='2008-11-27 04:00:59'WHERE} . q{ PL.APLTRACT_GARDENPLANT_ID = GC.APLTRACT_GARDENPLANT_ID AND PL.} . q{APLTRACT_GARDENPLANT_ID = ABU.ID AND GC.MATCHING_POT = 0 AND GC.PERFORM_DIG=1} . q{ AND ABU.DIG = 6 AND ( ((SOIL-COST) > -80.0} . q{ AND BUGS < 60.0 AND (SOIL-COST) < 200.0) AND POTS < 10.0 )}, { DATABASE => {}, TABLE => { PL => 'GARDEN_CLUPL', GC => 'GARDENJOB', ABU => 'APLTRACT_GARDENPLANT', }, }, [qw(GARDEN_CLUPL GARDENJOB APLTRACT_GARDENPLANT)], 'Gets tables from query with aliases and comma-join', ); test_query( q{SELECT count(*) AS count_all FROM `impact_actions` LEFT OUTER JOIN } . q{recommended_change_events ON (impact_actions.event_id = } . q{recommended_change_events.event_id) LEFT OUTER JOIN } . q{recommended_change_aments ON (impact_actions.ament_id = } . q{recommended_change_aments.ament_id) WHERE (impact_actions.user_id = 71058 } # An old version of the regex used to think , was the precursor to a # table name, so it would pull out 7,8,9,10,11 as table names. . q{AND (impact_actions.action_type IN (4,7,8,9,10,11) AND } . q{(impact_actions.change_id = 2699 OR recommended_change_events.change_id = } . q{2699 OR recommended_change_aments.change_id = 2699)))}, { DATABASE => {}, TABLE => { 'impact_actions' => 'impact_actions', 'recommended_change_events' => 'recommended_change_events', 'recommended_change_aments' => 'recommended_change_aments', }, }, [qw(`impact_actions` recommended_change_events recommended_change_aments)], 'Does not think IN() list has table names', ); test_query( 'INSERT INTO my.tbl VALUES("I got this FROM the newspaper today")', { TABLE => { 'tbl' => 'tbl', }, DATABASE => { 'tbl' => 'my' }, }, [qw(my.tbl)], 'Not confused by quoted string' ); is_deeply( [ $qp->get_tables( q{REPLACE /*foo.bar:3/3*/ INTO checksum.checksum (db, tbl, } . q{chunk, boundaries, this_cnt, this_crc) SELECT 'foo', 'bar', } . q{2 AS chunk_num, '`id` >= 2166633', COUNT(*) AS cnt, } . q{LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `id`, `created_by`, } . q{`created_date`, `updated_by`, `updated_date`, `ppc_provider`, } . q{`account_name`, `provider_account_id`, `campaign_name`, } . q{`provider_campaign_id`, `adgroup_name`, `provider_adgroup_id`, } . q{`provider_keyword_id`, `provider_ad_id`, `foo`, `reason`, } . q{`foo_bar_bazz_id`, `foo_bar_baz`, CONCAT(ISNULL(`created_by`), } . q{ISNULL(`created_date`), ISNULL(`updated_by`), ISNULL(`updated_date`), } . q{ISNULL(`ppc_provider`), ISNULL(`account_name`), } . q{ISNULL(`provider_account_id`), ISNULL(`campaign_name`), } . q{ISNULL(`provider_campaign_id`), ISNULL(`adgroup_name`), } . q{ISNULL(`provider_adgroup_id`), ISNULL(`provider_keyword_id`), } . q{ISNULL(`provider_ad_id`), ISNULL(`foo`), ISNULL(`reason`), } . q{ISNULL(`foo_base_foo_id`), ISNULL(`fooe_foo_id`)))) AS UNSIGNED)), 10, } . q{16)) AS crc FROM `foo`.`bar` USE INDEX (`PRIMARY`) WHERE } . q{(`id` >= 2166633); } ) ], [qw(checksum.checksum `foo`.`bar`)], 'gets tables from nasty checksum query', ); is_deeply( [ $qp->get_tables(q{SELECT STRAIGHT_JOIN distinct foo, bar FROM A, B, C}) ], [qw(A B C)], 'gets tables from STRAIGHT_JOIN', ); is_deeply( [ $qp->get_tables( 'replace into checksum.checksum select `last_update`, `foo` from foo.foo' ) ], [qw(checksum.checksum foo.foo)], 'gets tables with reserved words' ); is_deeply( [ $qp->get_tables( 'SELECT * FROM (SELECT * FROM foo WHERE UserId = 577854809 ORDER BY foo DESC) q1 GROUP BY foo ORDER BY bar DESC LIMIT 3' ) ], [qw(foo)], 'get_tables on simple subquery' ); is_deeply( [ $qp->get_tables( 'INSERT INTO my.tbl VALUES("I got this from the newspaper")') ], [qw(my.tbl)], 'Not confused by quoted string' ); is_deeply( [ $qp->get_tables('create table db.tbl (i int)') ], [qw(db.tbl)], 'get_tables: CREATE TABLE' ); is_deeply( [ $qp->get_tables('create TEMPORARY table db.tbl2 (i int)') ], [qw(db.tbl2)], 'get_tables: CREATE TEMPORARY TABLE' ); is_deeply( [ $qp->get_tables('create table if not exists db.tbl (i int)') ], [qw(db.tbl)], 'get_tables: CREATE TABLE IF NOT EXISTS' ); is_deeply( [ $qp->get_tables('create TEMPORARY table IF NOT EXISTS db.tbl3 (i int)') ], [qw(db.tbl3)], 'get_tables: CREATE TEMPORARY TABLE IF NOT EXISTS' ); is_deeply( [ $qp->get_tables( 'CREATE TEMPORARY TABLE `foo` AS select * from bar where id = 1') ], [qw(bar)], 'get_tables: CREATE TABLE ... SELECT' ); is_deeply( [ $qp->get_tables('ALTER TABLE db.tbl ADD COLUMN (j int)') ], [qw(db.tbl)], 'get_tables: ALTER TABLE' ); is_deeply( [ $qp->get_tables('DROP TABLE db.tbl') ], [qw(db.tbl)], 'get_tables: DROP TABLE' ); is_deeply( [ $qp->get_tables('truncate table db.tbl') ], [qw(db.tbl)], 'get_tables: TRUNCATE TABLE' ); is_deeply( [ $qp->get_tables('create database foo') ], [], 'get_tables: CREATE DATABASE (no tables)' ); is_deeply( [ $qp->get_tables( 'INSERT INTO `foo` (`s`,`from`,`t`,`p`) VALVUES ("not","me","foo",1)' ) ], [qw(`foo`)], 'Throws out suspicious table names' ); ok( $qp->has_derived_table('select * from ( select 1) as x'), 'simple derived' ); ok( $qp->has_derived_table('select * from a join ( select 1) as x'), 'join, derived' ); ok( $qp->has_derived_table('select * from a join b, (select 1) as x'), 'comma join, derived' ); is( $qp->has_derived_table('select * from foo'), '', 'no derived' ); is( $qp->has_derived_table('select * from foo where a in(select a from b)'), '', 'no derived on correlated' ); # ############################################################################# # Test split(). # ############################################################################# is_deeply( [ $qp->split('SELECT * FROM db.tbl WHERE id = 1') ], [ 'SELECT * FROM db.tbl WHERE id = 1', ], 'split 1 statement, SELECT' ); my $sql = 'replace into db1.tbl2 (dt, hr) select foo, bar from db2.tbl2 where id = 1 group by foo'; is_deeply( [ $qp->split($sql) ], [ 'replace into db1.tbl2 (dt, hr) ', 'select foo, bar from db2.tbl2 where id = 1 group by foo', ], 'split 2 statements, REPLACE ... SELECT' ); $sql = 'insert into db1.tbl 1 (dt,hr) select dt,hr from db2.tbl2 where foo = 1'; is_deeply( [ $qp->split($sql) ], [ 'insert into db1.tbl 1 (dt,hr) ', 'select dt,hr from db2.tbl2 where foo = 1', ], 'split 2 statements, INSERT ... SELECT' ); $sql = 'create table if not exists db.tbl (primary key (lmp), id int not null unique key auto_increment, lmp datetime)'; is_deeply( [ $qp->split($sql) ], [ $sql, ], 'split 1 statement, CREATE' ); $sql = "select num from db.tbl where replace(col,' ','') = 'foo'"; is_deeply( [ $qp->split($sql) ], [ $sql, ], 'split 1 statement, SELECT with REPLACE() function' ); $sql = " INSERT INTO db.tbl (i, t, c, m) VALUES (237527, '', 0, '1 rows')"; is_deeply( [ $qp->split($sql) ], [ "INSERT INTO db.tbl (i, t, c, m) VALUES (237527, '', 0, '1 rows')", ], 'split 1 statement, INSERT with leading newline and spaces' ); $sql = 'create table db1.tbl1 SELECT id FROM db2.tbl2 WHERE time = 46881;'; is_deeply( [ $qp->split($sql) ], [ 'create table db1.tbl1 ', 'SELECT id FROM db2.tbl2 WHERE time = 46881;', ], 'split 2 statements, CREATE ... SELECT' ); $sql = "/*MAIN FUNCTION
*/ insert into p.b317 (foo) select p.b1927.rtb as pr /* inner join pa7.r on pr.pd = c.pd */ inner join m.da on da.hr=p.hr and da.node=pr.node ;"; is_deeply( [ $qp->split($sql) ], [ 'insert into p.b317 (foo) ', 'select p.b1927.rtb as pr inner join m.da on da.hr=p.hr and da.node=pr.node ;', ], 'split statements with comment blocks' ); $sql = "insert into test1.tbl6 (day) values ('monday') on duplicate key update metric11 = metric11 + 1"; is_deeply( [ $qp->split($sql) ], [ $sql, ], 'split "on duplicate key"' ); # ############################################################################# # Test split_subquery(). # ############################################################################# $sql = 'SELECT * FROM t1 WHERE column1 = (SELECT column1 FROM t2);'; is_deeply( [ $qp->split_subquery($sql) ], [ 'SELECT * FROM t1 WHERE column1 = (__subquery_1)', '(SELECT column1 FROM t2)', ], 'split_subquery() basic' ); # ############################################################################# # Test query_type(). # ############################################################################# is_deeply( $qp->query_type( 'select * from foo where id=1', $qr ), { type => 'SELECT', rw => 'read', }, 'query_type() select' ); is_deeply( $qp->query_type( '/* haha! */ select * from foo where id=1', $qr ), { type => 'SELECT', rw => 'read', }, 'query_type() select with leading /* comment */' ); is_deeply( $qp->query_type( 'insert into foo values (1, 2)', $qr ), { type => 'INSERT', rw => 'write', }, 'query_type() insert' ); is_deeply( $qp->query_type( 'delete from foo where bar=1', $qr ), { type => 'DELETE', rw => 'write', }, 'query_type() delete' ); is_deeply( $qp->query_type( 'update foo set bar="foo" where 1', $qr ), { type => 'UPDATE', rw => 'write', }, 'query_type() update' ); is_deeply( $qp->query_type( 'truncate table bar', $qr ), { type => 'TRUNCATE TABLE', rw => 'write', }, 'query_type() truncate' ); is_deeply( $qp->query_type( 'alter table foo add column (i int)', $qr ), { type => 'ALTER TABLE', rw => 'write', }, 'query_type() alter' ); is_deeply( $qp->query_type( 'drop table foo', $qr ), { type => 'DROP TABLE', rw => 'write', }, 'query_type() drop' ); is_deeply( $qp->query_type( 'show tables', $qr ), { type => 'SHOW TABLES', rw => undef, }, 'query_type() show tables' ); is_deeply( $qp->query_type( 'show fields from foo', $qr ), { type => 'SHOW FIELDS', rw => undef, }, 'query_type() show fields' ); # ############################################################################# # Issue 563: Lock tables is not distilled # ############################################################################# is_deeply( [ $qp->get_tables('LOCK TABLES foo READ') ], [qw(foo)], 'LOCK TABLES foo READ' ); is_deeply( [ $qp->get_tables('LOCK TABLES foo WRITE') ], [qw(foo)], 'LOCK TABLES foo WRITE' ); is_deeply( [ $qp->get_tables('LOCK TABLES foo READ, bar WRITE') ], [qw(foo bar)], 'LOCK TABLES foo READ, bar WRITE' ); is_deeply( [ $qp->get_tables('LOCK TABLES foo AS als WRITE') ], [qw(foo)], 'LOCK TABLES foo AS als WRITE' ); is_deeply( [ $qp->get_tables('LOCK TABLES foo AS als1 READ, bar AS als2 WRITE') ], [qw(foo bar)], 'LOCK TABLES foo AS als READ, bar AS als2 WRITE' ); is_deeply( [ $qp->get_tables('LOCK TABLES foo als WRITE') ], [qw(foo)], 'LOCK TABLES foo als WRITE' ); is_deeply( [ $qp->get_tables('LOCK TABLES foo als1 READ, bar als2 WRITE') ], [qw(foo bar)], 'LOCK TABLES foo als READ, bar als2 WRITE' ); $sql = "CREATE TEMPORARY TABLE mk_upgrade AS SELECT col1, col2 FROM foo, bar WHERE id = 1"; is_deeply( [ $qp->get_tables($sql) ], [qw(foo bar)], 'Get tables from special case multi-line query' ); is_deeply( [ $qp->get_tables('select * from (`mytable`)') ], [qw(`mytable`)], 'Get tables when there are parens around table name (issue 781)', ); is_deeply( [ $qp->get_tables('select * from (select * from mytable) t') ], [qw(mytable)], 'Does not consider subquery SELECT as a table (issue 781)', ); is_deeply( [ $qp->get_tables('lock tables t1 as t5 read local, t2 low_priority write') ], [qw(t1 t2)], 'get_tables works for lowercased LOCK TABLES', ); is_deeply( [ $qp->get_tables("LOAD DATA INFILE '/tmp/foo.txt' INTO TABLE db.tbl") ], [qw(db.tbl)], "LOAD DATA db.tbl" ); # ############################################################################# # Done. # ############################################################################# done_testing;