updated streamline-setup v2

This commit is contained in:
2025-01-15 08:53:49 -08:00
committed by alec.turner
parent a2ce9248f0
commit 4b569f81b0
20228 changed files with 2932048 additions and 63204 deletions
@@ -0,0 +1,260 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Builder;
use Generator;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class AlterStatementTest extends TestCase
{
public function testBuilder(): void
{
$query = 'ALTER TABLE `actor` ' .
'ADD PRIMARY KEY (`actor_id`), ' .
'ADD KEY `idx_actor_last_name` (`last_name`)';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals($query, $stmt->build());
}
public function testBuilderWithExpression(): void
{
$query = 'ALTER TABLE `table` '
. 'ADD UNIQUE KEY `functional_index`'
. ' (`field1`,`field2`, (IFNULL(`field3`,0)))';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals($query, $stmt->build());
}
public function testBuilderWithComments(): void
{
$query = 'ALTER /* comment */ TABLE `actor` ' .
'ADD PRIMARY KEY (`actor_id`), -- comment at the end of the line' . "\n" .
'ADD KEY `idx_actor_last_name` (`last_name`) -- and that is the last comment.';
$expectedQuery = 'ALTER TABLE `actor` ' .
'ADD PRIMARY KEY (`actor_id`), ' .
'ADD KEY `idx_actor_last_name` (`last_name`)';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals($expectedQuery, $stmt->build());
}
public function testBuilderWithCommentsOnOptions(): void
{
$query = 'ALTER EVENT `myEvent` /* comment */ ' .
'ON SCHEDULE -- Comment at the end of the line' . "\n" .
'AT "2023-01-01 01:23:45"';
$expectedQuery = 'ALTER EVENT `myEvent` ' .
'ON SCHEDULE AT "2023-01-01 01:23:45"';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals($expectedQuery, $stmt->build());
}
public function testBuilderCompressed(): void
{
$query = 'ALTER TABLE `user` CHANGE `message` `message` TEXT COMPRESSED';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals($query, $stmt->build());
}
public function testBuilderPartitions(): void
{
$parser = new Parser('ALTER TABLE t1 PARTITION BY HASH(id) PARTITIONS 8');
$stmt = $parser->statements[0];
$this->assertEquals('ALTER TABLE t1 PARTITION BY HASH(id) PARTITIONS 8', $stmt->build());
$parser = new Parser('ALTER TABLE t1 ADD PARTITION (PARTITION p3 VALUES LESS THAN (2002))');
$stmt = $parser->statements[0];
$this->assertEquals(
"ALTER TABLE t1 ADD PARTITION (\n" .
"PARTITION p3 VALUES LESS THAN (2002)\n" .
')',
$stmt->build()
);
$parser = new Parser('ALTER TABLE p PARTITION BY LINEAR KEY ALGORITHM=2 (id) PARTITIONS 32;');
$stmt = $parser->statements[0];
$this->assertEquals(
'ALTER TABLE p PARTITION BY LINEAR KEY ALGORITHM=2 (id) PARTITIONS 32',
$stmt->build()
);
$parser = new Parser('ALTER TABLE t1 DROP PARTITION p0, p1;');
$stmt = $parser->statements[0];
$this->assertEquals(
'ALTER TABLE t1 DROP PARTITION p0, p1',
$stmt->build()
);
$parser = new Parser(
'ALTER TABLE trips PARTITION BY RANGE (MONTH(trip_date))'
. ' (' . "\n"
. ' PARTITION p01 VALUES LESS THAN (02),' . "\n"
. ' PARTITION p02 VALUES LESS THAN (03),' . "\n"
. ' PARTITION p03 VALUES LESS THAN (04),' . "\n"
. ' PARTITION p04 VALUES LESS THAN (05),' . "\n"
. ' PARTITION p05 VALUES LESS THAN (06),' . "\n"
. ' PARTITION p06 VALUES LESS THAN (07),' . "\n"
. ' PARTITION p07 VALUES LESS THAN (08),' . "\n"
. ' PARTITION p08 VALUES LESS THAN (09),' . "\n"
. ' PARTITION p09 VALUES LESS THAN (10),' . "\n"
. ' PARTITION p10 VALUES LESS THAN (11),' . "\n"
. ' PARTITION p11 VALUES LESS THAN (12),' . "\n"
. ' PARTITION p12 VALUES LESS THAN (13),' . "\n"
. ' PARTITION pmaxval VALUES LESS THAN MAXVALUE' . "\n"
. ');'
);
$stmt = $parser->statements[0];
$this->assertEquals(
'ALTER TABLE trips PARTITION BY RANGE (MONTH(trip_date)) (' . "\n"
. 'PARTITION p01 VALUES LESS THAN (02),' . "\n"
. 'PARTITION p02 VALUES LESS THAN (03),' . "\n"
. 'PARTITION p03 VALUES LESS THAN (04),' . "\n"
. 'PARTITION p04 VALUES LESS THAN (05),' . "\n"
. 'PARTITION p05 VALUES LESS THAN (06),' . "\n"
. 'PARTITION p06 VALUES LESS THAN (07),' . "\n"
. 'PARTITION p07 VALUES LESS THAN (08),' . "\n"
. 'PARTITION p08 VALUES LESS THAN (09),' . "\n"
. 'PARTITION p09 VALUES LESS THAN (10),' . "\n"
. 'PARTITION p10 VALUES LESS THAN (11),' . "\n"
. 'PARTITION p11 VALUES LESS THAN (12),' . "\n"
. 'PARTITION p12 VALUES LESS THAN (13),' . "\n"
. 'PARTITION pmaxval VALUES LESS THAN MAXVALUE' . "\n"
. ')',
$stmt->build()
);
}
public function testBuilderEventWithDefiner(): void
{
$query = 'ALTER DEFINER=user EVENT myEvent ENABLE';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals($query, $stmt->build());
}
/**
* @return Generator<string, array{string}>
*/
public static function provideBuilderForRenameColumn(): Generator
{
$query = 'ALTER TABLE myTable RENAME COLUMN a TO b';
yield 'Single RENAME COLUMN' => [$query];
$query = 'ALTER TABLE myTable RENAME COLUMN a TO b, RENAME COLUMN b TO a';
yield 'Multiple RENAME COLUMN' => [$query];
$query = 'ALTER TABLE myTable ' .
'RENAME COLUMN a TO b, ' .
'RENAME COLUMN b TO a, ' .
'RENAME INDEX oldIndex TO newIndex, ' .
'RENAME TO newTable';
yield 'Mixed RENAME COLUMN + RENAME INDEX + RENAME table' => [$query];
$query = 'ALTER TABLE myTable ' .
'RENAME TO newTable, ' .
'RENAME INDEX oldIndex TO newIndex, ' .
'RENAME COLUMN b TO a, ' .
'RENAME COLUMN a TO b';
yield 'Mixed RENAME table + RENAME INDEX + RENAME COLUMNS' => [$query];
}
/**
* @dataProvider provideBuilderForRenameColumn
*/
public function testBuilderRenameColumn(string $query): void
{
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals($query, $stmt->build());
}
/**
* @return Generator<string, array{string}>
*/
public static function provideBuilderForAlterRoutine(): Generator
{
$query = 'ALTER FUNCTION func_name COMMENT "test"';
yield 'Function with only comment' => [$query];
$query = 'ALTER FUNCTION func_name LANGUAGE SQL';
yield 'Function with only language' => [$query];
$query = 'ALTER FUNCTION func_name COMMENT "test" LANGUAGE SQL CONTAINS SQL SQL SECURITY DEFINER';
yield 'Function with all options combinations #1' => [$query];
$query = 'ALTER FUNCTION func_name COMMENT "test" LANGUAGE SQL NO SQL SQL SECURITY INVOKER';
yield 'Function with all options combinations #2' => [$query];
$query = 'ALTER FUNCTION func_name COMMENT "test" LANGUAGE SQL READS SQL DATA';
yield 'Function with all remaining options #1' => [$query];
$query = 'ALTER FUNCTION func_name COMMENT "test" LANGUAGE SQL MODIFIES SQL DATA';
yield 'Function with all remaining options #2' => [$query];
$query = 'ALTER PROCEDURE func_name COMMENT "test"';
yield 'Procedure with only comment' => [$query];
$query = 'ALTER PROCEDURE proc_name LANGUAGE SQL';
yield 'Procedure with only language' => [$query];
$query = 'ALTER PROCEDURE proc_name COMMENT "test" LANGUAGE SQL CONTAINS SQL SQL SECURITY DEFINER';
yield 'Procedure with all options combinations #1' => [$query];
$query = 'ALTER PROCEDURE proc_name COMMENT "test" LANGUAGE SQL NO SQL SQL SECURITY INVOKER';
yield 'Procedure with all options combinations #2' => [$query];
$query = 'ALTER PROCEDURE proc_name COMMENT "test" LANGUAGE SQL READS SQL DATA';
yield 'Procedure with all remaining options #1' => [$query];
$query = 'ALTER PROCEDURE proc_name COMMENT "test" LANGUAGE SQL MODIFIES SQL DATA';
yield 'Procedure with all remaining options #2' => [$query];
}
/**
* @dataProvider provideBuilderForAlterRoutine
*/
public function testBuilderForAlterRoutine(string $query): void
{
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals($query, $stmt->build());
}
}
@@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Builder;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class CallStatementTest extends TestCase
{
public function testBuilder(): void
{
$query = 'CALL foo()';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals($query, $stmt->build());
}
public function testBuilderShort(): void
{
$query = 'CALL foo';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals($query . '()', $stmt->build());
}
public function testBuilderWithDbName(): void
{
$query = 'CALL mydb.foo()';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals($query, $stmt->build());
}
public function testBuilderWithDbNameShort(): void
{
$query = 'CALL mydb.foo';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals($query . '()', $stmt->build());
}
public function testBuilderWithDbNameAndParams(): void
{
$query = 'CALL mydb.foo(@bar, @baz);';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals('CALL mydb.foo(@bar,@baz)', $stmt->build());
}
public function testBuilderMultiCallsShort(): void
{
$query = 'call e;call f';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals('CALL e()', $stmt->build());
$stmt = $parser->statements[1];
$this->assertEquals('CALL f()', $stmt->build());
}
public function testBuilderMultiCalls(): void
{
$query = 'call e();call f';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals('CALL e()', $stmt->build());
$stmt = $parser->statements[1];
$this->assertEquals('CALL f()', $stmt->build());
}
public function testBuilderMultiCallsArgs(): void
{
$query = 'call e("foo");call f';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals('CALL e("foo")', $stmt->build());
$stmt = $parser->statements[1];
$this->assertEquals('CALL f()', $stmt->build());
}
}
@@ -0,0 +1,845 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Builder;
use PhpMyAdmin\SqlParser\Components\CreateDefinition;
use PhpMyAdmin\SqlParser\Components\DataType;
use PhpMyAdmin\SqlParser\Components\Expression;
use PhpMyAdmin\SqlParser\Components\Key;
use PhpMyAdmin\SqlParser\Components\OptionsArray;
use PhpMyAdmin\SqlParser\Components\ParameterDefinition;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Statements\CreateStatement;
use PhpMyAdmin\SqlParser\Tests\TestCase;
use PhpMyAdmin\SqlParser\TokensList;
class CreateStatementTest extends TestCase
{
public function testBuilder(): void
{
$parser = new Parser('CREATE USER "jeffrey"@"localhost" IDENTIFIED BY "mypass"');
$stmt = $parser->statements[0];
$this->assertEquals(
'CREATE USER "jeffrey"@"localhost" IDENTIFIED BY "mypass"',
$stmt->build()
);
}
public function testBuilderDatabase(): void
{
// CREATE DATABASE ...
$parser = new Parser(
'CREATE DATABASE `mydb` ' .
'DEFAULT CHARACTER SET = utf8 DEFAULT COLLATE = utf8_general_ci'
);
$stmt = $parser->statements[0];
$this->assertEquals(
'CREATE DATABASE `mydb` ' .
'DEFAULT CHARACTER SET=utf8 DEFAULT COLLATE=utf8_general_ci',
$stmt->build()
);
// CREATE SCHEMA ...
$parser = new Parser('CREATE SCHEMA `mydb` DEFAULT CHARACTER SET = utf8 DEFAULT COLLATE = utf8_general_ci');
$stmt = $parser->statements[0];
$this->assertEquals(
'CREATE SCHEMA `mydb` ' .
'DEFAULT CHARACTER SET=utf8 DEFAULT COLLATE=utf8_general_ci',
$stmt->build()
);
}
public function testBuilderDefaultInt(): void
{
$parser = new Parser(
'CREATE TABLE IF NOT EXISTS t1 (' .
" c1 int(11) NOT NULL DEFAULT '0' COMMENT 'xxx'" .
') ENGINE=MyISAM'
);
$stmt = $parser->statements[0];
$this->assertEquals(
"CREATE TABLE IF NOT EXISTS t1 (\n" .
" `c1` int(11) NOT NULL DEFAULT '0' COMMENT 'xxx'\n" .
') ENGINE=MyISAM',
$stmt->build()
);
}
public function testBuilderWithComments(): void
{
$parser = new Parser('CREATE TABLE tab1 (`col1` TIMESTAMP /*!40100 DEFAULT NULL */)');
$stmt = $parser->statements[0];
$this->assertEquals(
// TODO: fix with https://github.com/phpmyadmin/sql-parser/issues/256
"CREATE TABLE tab1 (\n `col1` timestamp DEFAULT NULL\n) ",
$stmt->build()
);
}
public function testBuilderCompressed(): void
{
$parser = new Parser('CREATE TABLE users ( user_id int ) PAGE_COMPRESSED=1 PAGE_COMPRESSION_LEVEL=9;');
$stmt = $parser->statements[0];
$this->assertEquals(
"CREATE TABLE users (\n `user_id` int\n) PAGE_COMPRESSED=1 PAGE_COMPRESSION_LEVEL=9",
$stmt->build()
);
}
public function testBuilderCollate(): void
{
$parser = new Parser(
'CREATE TABLE IF NOT EXISTS t1 (' .
" c1 varchar(11) NOT NULL DEFAULT '0' COLLATE 'utf8_czech_ci' COMMENT 'xxx'" .
') ENGINE=MyISAM'
);
$stmt = $parser->statements[0];
$this->assertEquals(
"CREATE TABLE IF NOT EXISTS t1 (\n" .
" `c1` varchar(11) NOT NULL DEFAULT '0' COLLATE 'utf8_czech_ci' COMMENT 'xxx'\n" .
') ENGINE=MyISAM',
$stmt->build()
);
}
public function testBuilderDefaultComment(): void
{
$parser = new Parser(
'CREATE TABLE `wp_audio` (' .
" `somedata` int(11) DEFAULT NULL COMMENT 'ma data', " .
" `someinfo` int(11) DEFAULT NULL COMMENT 'ma info' " .
' )'
);
$stmt = $parser->statements[0];
$this->assertEquals(
"CREATE TABLE `wp_audio` (\n" .
" `somedata` int(11) DEFAULT NULL COMMENT 'ma data',\n" .
" `someinfo` int(11) DEFAULT NULL COMMENT 'ma info'\n" .
') ',
$stmt->build()
);
}
public function testBuilderTable(): void
{
/* Assertion 1 */
$stmt = new CreateStatement();
$stmt->name = new Expression('', 'test', '');
$stmt->options = new OptionsArray(['TABLE']);
$stmt->fields = [
new CreateDefinition(
'id',
new OptionsArray(['NOT NULL', 'AUTO_INCREMENT']),
new DataType('INT', [11], new OptionsArray(['UNSIGNED']))
),
new CreateDefinition(
'',
null,
new Key('', [['name' => 'id']], 'PRIMARY KEY')
),
];
$this->assertEquals(
"CREATE TABLE `test` (\n" .
" `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,\n" .
" PRIMARY KEY (`id`)\n" .
') ',
$stmt->build()
);
/* Assertion 2 */
$query =
"CREATE TABLE `jos_core_acl_aro` (\n" .
" `id` int(11) NOT NULL,\n" .
" `section_value` varchar(240) NOT NULL DEFAULT '0',\n" .
" `value` varchar(240) NOT NULL DEFAULT '',\n" .
" `order_value` int(11) NOT NULL DEFAULT '0',\n" .
" `name` varchar(255) NOT NULL DEFAULT '',\n" .
" `hidden` int(11) NOT NULL DEFAULT '0',\n" .
" PRIMARY KEY (`id`),\n" .
" UNIQUE KEY `jos_section_value_value_aro` (`section_value`(100),`value`(15)) USING BTREE,\n" .
" KEY `jos_gacl_hidden_aro` (`hidden`)\n" .
') ENGINE=InnoDB DEFAULT CHARSET=latin1';
$parser = new Parser($query);
$this->assertEquals($query, $parser->statements[0]->build());
/* Assertion 3 */
$query = 'CREATE TABLE `table_copy` LIKE `old_table`';
$parser = new Parser($query);
$this->assertEquals($query, $parser->statements[0]->build());
/* Assertion 4 */
$query =
"CREATE TABLE `aa` (\n" .
" `id` int(11) NOT NULL,\n" .
" `rTime` timestamp(3) NOT NULL DEFAULT '0000-00-00 00:00:00.000' ON UPDATE CURRENT_TIMESTAMP(3),\n" .
" PRIMARY KEY (`id`)\n" .
') ENGINE=InnoDB DEFAULT CHARSET=latin1';
$parser = new Parser($query);
$this->assertEquals($query, $parser->statements[0]->build());
/* Assertion 5 */
$parser = new Parser(
'CREATE table table_name WITH' .
' cte (col1) AS ( SELECT 1 UNION ALL SELECT 2 )' .
' SELECT col1 FROM cte'
);
$stmt = $parser->statements[0];
$this->assertEquals(
'CREATE TABLE table_name WITH' .
' cte(col1) AS (SELECT 1 UNION ALL SELECT 2)' .
' SELECT col1 FROM cte',
$stmt->build()
);
}
public function testBuilderPartitions(): void
{
/* Assertion 1 */
$query = 'CREATE TABLE ts (' . "\n"
. ' `id` int,' . "\n"
. ' `purchased` date' . "\n"
. ') ' . "\n"
. 'PARTITION BY RANGE(YEAR(purchased))' . "\n"
. 'PARTITIONS 3' . "\n"
. 'SUBPARTITION BY HASH(TO_DAYS(purchased))' . "\n"
. 'SUBPARTITIONS 2' . "\n"
. '(' . "\n"
. 'PARTITION p0 VALUES LESS THAN (1990) (' . "\n"
. 'SUBPARTITION s0,' . "\n"
. 'SUBPARTITION s1' . "\n"
. '),' . "\n"
. 'PARTITION p1 VALUES LESS THAN (2000) (' . "\n"
. 'SUBPARTITION s2,' . "\n"
. 'SUBPARTITION s3' . "\n"
. '),' . "\n"
. 'PARTITION p2 VALUES LESS THAN MAXVALUE (' . "\n"
. 'SUBPARTITION s4,' . "\n"
. 'SUBPARTITION s5' . "\n"
. ')' . "\n"
. ')';
$parser = new Parser($query);
$this->assertEquals($query, $parser->statements[0]->build());
/* Assertion 2 */
$query = 'CREATE TABLE `pma_test` (' . "\n"
. ' `test_id` int(32) NOT NULL,' . "\n"
. ' `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP' . "\n"
. ') ENGINE=InnoDB DEFAULT CHARSET=utf8' . "\n"
. 'PARTITION BY RANGE (test_id)' . "\n"
. '(' . "\n"
. 'PARTITION p0 VALUES LESS THAN (250000) ENGINE=InnoDB,' . "\n"
. 'PARTITION p1 VALUES LESS THAN (500000) ENGINE=InnoDB,' . "\n"
. 'PARTITION p2 VALUES LESS THAN (750000) ENGINE=InnoDB,' . "\n"
. 'PARTITION p3 VALUES LESS THAN (1000000) ENGINE=InnoDB,' . "\n"
. 'PARTITION p4 VALUES LESS THAN (1250000) ENGINE=InnoDB,' . "\n"
. 'PARTITION p5 VALUES LESS THAN (1500000) ENGINE=InnoDB,' . "\n"
. 'PARTITION p6 VALUES LESS THAN MAXVALUE ENGINE=InnoDB' . "\n"
. ')';
$parser = new Parser($query);
$this->assertEquals($query, $parser->statements[0]->build());
}
/**
* @return string[][]
*/
public static function partitionQueriesProvider(): array
{
$subPartitions = <<<EOT
CREATE TABLE `ts` (
`id` int(11) DEFAULT NULL,
`purchased` date DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
PARTITION BY RANGE (YEAR(purchased))
SUBPARTITION BY HASH (TO_DAYS(purchased))
(
PARTITION p0 VALUES LESS THAN (1990) (
SUBPARTITION s0 ENGINE=InnoDB,
SUBPARTITION s1 ENGINE=InnoDB
),
PARTITION p1 VALUES LESS THAN (2000) (
SUBPARTITION s2 ENGINE=InnoDB,
SUBPARTITION s3 ENGINE=InnoDB
),
PARTITION p2 VALUES LESS THAN MAXVALUE (
SUBPARTITION s4 ENGINE=InnoDB,
SUBPARTITION s5 ENGINE=InnoDB
)
)
EOT;
$partitions = <<<EOT
CREATE TABLE ptest (
`event_date` date NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC
PARTITION BY HASH (TO_DAYS(event_date))
(
PARTITION p0 ENGINE=InnoDB,
PARTITION p1 ENGINE=InnoDB,
PARTITION p2 ENGINE=InnoDB,
PARTITION p3 ENGINE=InnoDB,
PARTITION p4 ENGINE=InnoDB
)
EOT;
return ['subpartitions' => [$subPartitions], 'partitions' => [$partitions]];
}
/**
* @dataProvider partitionQueriesProvider
*/
public function testBuilderPartitionsEngine(string $query): void
{
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals($query, $stmt->build());
}
public function testBuilderView(): void
{
$parser = new Parser(
'CREATE OR REPLACE VIEW xviewmytable AS SELECT mytable.id '
. 'AS id, mytable.personid AS personid FROM mytable '
. 'WHERE (mytable.birth > \'1990-01-19\') GROUP BY mytable.personid ;'
);
$stmt = $parser->statements[0];
$this->assertEquals(
'CREATE OR REPLACE VIEW xviewmytable AS SELECT mytable.id '
. 'AS `id`, mytable.personid AS `personid` FROM mytable '
. 'WHERE (mytable.birth > \'1990-01-19\') GROUP BY mytable.personid ',
$stmt->build()
);
$parser = new Parser(
'CREATE VIEW myView (vid, vfirstname) AS ' .
'SELECT id, first_name FROM employee WHERE id = 1'
);
$stmt = $parser->statements[0];
$this->assertEquals(
'CREATE VIEW myView (vid, vfirstname) AS ' .
'SELECT id, first_name FROM employee WHERE id = 1 ',
$stmt->build()
);
$parser = new Parser(
'CREATE OR REPLACE VIEW myView (vid, vfirstname) AS ' .
'SELECT id, first_name FROM employee WHERE id = 1'
);
$stmt = $parser->statements[0];
$this->assertEquals(
'CREATE OR REPLACE VIEW myView (vid, vfirstname) AS ' .
'SELECT id, first_name FROM employee WHERE id = 1 ',
$stmt->build()
);
// Assert the builder can build wrong syntax select expressions
$parser = new Parser(
'CREATE OR REPLACE VIEW myView (vid, vfirstname) AS ' .
'SELECT id, first_name, FROMzz employee WHERE id = 1'
);
$stmt = $parser->statements[0];
$this->assertEquals(
'CREATE OR REPLACE VIEW myView (vid, vfirstname) AS ' .
'SELECT id, first_name, FROMzz employee WHERE id = 1 ',
$stmt->build()
);
$parser = new Parser(
'CREATE OR REPLACE VIEW myView (vid, vfirstname) AS ' .
'SELECT id, first_name, FROMzz employee WHERE id = 1 ' .
'UNION ' .
'SELECT id, first_name, FROMzz employee WHERE id = 2 '
);
$stmt = $parser->statements[0];
$this->assertEquals(
'CREATE OR REPLACE VIEW myView (vid, vfirstname) AS ' .
'SELECT id, first_name, FROMzz employee WHERE id = 1 ' .
'UNION ' .
'SELECT id, first_name, FROMzz employee WHERE id = 2 ',
$stmt->build()
);
$parser = new Parser('CREATE VIEW `view_programlevelpartner` AS SELECT `p`.`country_id`'
. 'AS `country_id` FROM `program_level_partner` `p` ORDER BY `p`.`id` asc');
$stmt = $parser->statements[0];
$this->assertEquals(
'CREATE VIEW `view_programlevelpartner` AS SELECT `p`.`country_id`'
. ' AS `country_id` FROM `program_level_partner` AS `p` ORDER BY `p`.`id` ASC ',
$stmt->build()
);
$parser = new Parser('CREATE VIEW `view_zg_bycountry` AS '
. 'SELECT `d`.`zg_id` FROM `view_zg_value` AS `d` GROUP BY `d`.`ind_id`;');
$stmt = $parser->statements[0];
$this->assertEquals(
'CREATE VIEW `view_zg_bycountry` AS '
. 'SELECT `d`.`zg_id` FROM `view_zg_value` AS `d` GROUP BY `d`.`ind_id` ',
$stmt->build()
);
$parser = new Parser('CREATE view view_name AS WITH aa(col1)'
. ' AS ( SELECT 1 UNION ALL SELECT 2 ) SELECT col1 FROM cte AS `d` ');
$stmt = $parser->statements[0];
$this->assertEquals(
'CREATE view view_name AS WITH aa(col1)'
. ' AS (SELECT 1 UNION ALL SELECT 2) SELECT col1 FROM cte AS `d` ',
$stmt->build()
);
}
public function testBuilderViewComplex(): void
{
$parser = new Parser(
'CREATE VIEW withclause AS' . "\n"
. "\n"
. 'WITH cte AS (' . "\n"
. 'SELECT p.name, p.shape' . "\n"
. 'FROM gis_all as p' . "\n"
. ')' . "\n"
. "\n"
. 'SELECT cte.*' . "\n"
. 'FROM cte' . "\n"
. 'CROSS JOIN gis_all;'
);
$stmt = $parser->statements[0];
$this->assertEquals(
'CREATE VIEW withclause AS '
. 'WITH cte AS ('
. 'SELECT p.name, p.shape '
. 'FROM gis_all AS `p`'
. ') '
. 'SELECT cte.* '
. 'FROM cte '
. 'CROSS JOIN gis_all ',
$stmt->build()
);
$parser = new Parser(
'CREATE VIEW withclause2 AS' . "\n"
. "\n"
. 'WITH cte AS (' . "\n"
. "\t" . 'SELECT p.name, p.shape' . "\n"
. "\t" . 'FROM gis_all as p' . "\n"
. '), cte2 AS (' . "\n"
. "\t" . 'SELECT p.name as n2, p.shape as sh2' . "\n"
. "\t" . 'FROM gis_all as p' . "\n"
. ')' . "\n"
. "\n"
. 'SELECT cte.*,cte2.*' . "\n"
. 'FROM cte,cte2' . "\n"
. 'CROSS JOIN gis_all;'
);
$stmt = $parser->statements[0];
$this->assertEquals(
'CREATE VIEW withclause2 AS '
. 'WITH cte AS ('
. 'SELECT p.name, p.shape'
. ' FROM gis_all AS `p`'
. '), cte2 AS ('
. 'SELECT p.name AS `n2`, p.shape AS `sh2`'
. ' FROM gis_all AS `p`'
. ')'
. ' SELECT cte.*, cte2.* '
. 'FROM cte, cte2'
. ' CROSS JOIN gis_all ',
$stmt->build()
);
}
public function testBuilderCreateProcedure(): void
{
$parser = new Parser(
'CREATE DEFINER=`root`@`%`'
. ' PROCEDURE `test2`(IN `_var` INT) DETERMINISTIC'
. ' MODIFIES SQL DATA SELECT _var'
);
/** @var CreateStatement $stmt */
$stmt = $parser->statements[0];
$this->assertSame(
'CREATE DEFINER=`root`@`%`'
. ' PROCEDURE `test2` (IN `_var` INT) DETERMINISTIC'
. ' MODIFIES SQL DATA SELECT _var',
$stmt->build()
);
$parser = new Parser(
'CREATE DEFINER=`root`@`%`'
. ' PROCEDURE `test2`(IN `_var` INT) NOT DETERMINISTIC NO SQL'
. ' SQL SECURITY INVOKER NO SQL SQL SECURITY INVOKER SELECT _var'
);
/** @var CreateStatement $stmt */
$stmt = $parser->statements[0];
$this->assertSame(
'CREATE DEFINER=`root`@`%`'
. ' PROCEDURE `test2` (IN `_var` INT) NOT DETERMINISTIC NO SQL'
. ' SQL SECURITY INVOKER NO SQL SQL SECURITY INVOKER SELECT _var',
$stmt->build()
);
$this->assertFalse($stmt->entityOptions->isEmpty());
$this->assertFalse($stmt->options->isEmpty());
$this->assertSame(
'DEFINER=`root`@`%` PROCEDURE',
$stmt->options->__toString()
);
$this->assertSame(
'`test2`',
$stmt->name->__toString()
);
$this->assertSame(
'(IN `_var` INT)',
ParameterDefinition::build($stmt->parameters)
);
$this->assertSame(
'NOT DETERMINISTIC NO SQL SQL SECURITY INVOKER NO SQL SQL SECURITY INVOKER',
$stmt->entityOptions->__toString()
);
$this->assertSame(
'SELECT _var',
TokensList::build($stmt->body)
);
}
public function testBuilderCreateFunction(): void
{
$parser = new Parser(
'DELIMITER $$' . "\n"
. 'CREATE DEFINER=`root`@`localhost`'
. ' FUNCTION `inventory_in_stock`(`p_inventory_id` INT) RETURNS tinyint(1)'
. ' READS SQL DATA'
. ' COMMENT \'My best function written by a friend\'\'s friend\''
. ' BEGIN' . "\n"
. ' DECLARE v_rentals INT;' . "\n"
. ' DECLARE v_out INT;' . "\n"
. "\n"
. ' ' . "\n"
. ' ' . "\n"
. "\n"
. ' SELECT COUNT(*) INTO v_rentals' . "\n"
. ' FROM rental' . "\n"
. ' WHERE inventory_id = p_inventory_id;' . "\n"
. "\n"
. ' IF v_rentals = 0 THEN' . "\n"
. ' RETURN TRUE;' . "\n"
. ' END IF;' . "\n"
. "\n"
. ' SELECT COUNT(rental_id) INTO v_out' . "\n"
. ' FROM inventory LEFT JOIN rental USING(inventory_id)' . "\n"
. ' WHERE inventory.inventory_id = p_inventory_id' . "\n"
. ' AND rental.return_date IS NULL;' . "\n"
. "\n"
. ' IF v_out > 0 THEN' . "\n"
. ' RETURN FALSE;' . "\n"
. ' ELSE' . "\n"
. ' RETURN TRUE;' . "\n"
. ' END IF;' . "\n"
. 'END'
);
/** @var CreateStatement $stmt */
$stmt = $parser->statements[0];
$this->assertSame(
'CREATE DEFINER=`root`@`localhost`'
. ' FUNCTION `inventory_in_stock` (`p_inventory_id` INT) RETURNS TINYINT(1)'
. ' READS SQL DATA'
. ' COMMENT \'My best function written by a friend\'\'s friend\''
. ' BEGIN' . "\n"
. ' DECLARE v_rentals INT;' . "\n"
. ' DECLARE v_out INT;' . "\n"
. "\n"
. ' ' . "\n"
. ' ' . "\n"
. "\n"
. ' SELECT COUNT(*) INTO v_rentals' . "\n"
. ' FROM rental' . "\n"
. ' WHERE inventory_id = p_inventory_id;' . "\n"
. "\n"
. ' IF v_rentals = 0 THEN' . "\n"
. ' RETURN TRUE;' . "\n"
. ' END IF;' . "\n"
. "\n"
. ' SELECT COUNT(rental_id) INTO v_out' . "\n"
. ' FROM inventory LEFT JOIN rental USING(inventory_id)' . "\n"
. ' WHERE inventory.inventory_id = p_inventory_id' . "\n"
. ' AND rental.return_date IS NULL;' . "\n"
. "\n"
. ' IF v_out > 0 THEN' . "\n"
. ' RETURN FALSE;' . "\n"
. ' ELSE' . "\n"
. ' RETURN TRUE;' . "\n"
. ' END IF;' . "\n"
. 'END',
$stmt->build()
);
$this->assertFalse($stmt->entityOptions->isEmpty());
$this->assertFalse($stmt->options->isEmpty());
$this->assertSame(
'DEFINER=`root`@`localhost` FUNCTION',
$stmt->options->__toString()
);
$this->assertSame(
'`inventory_in_stock`',
$stmt->name->__toString()
);
$this->assertSame(
'(`p_inventory_id` INT)',
ParameterDefinition::build($stmt->parameters)
);
$this->assertSame(
'READS SQL DATA COMMENT \'My best function written by a friend\'\'s friend\'',
$stmt->entityOptions->__toString()
);
$this->assertSame(
'BEGIN' . "\n"
. ' DECLARE v_rentals INT;' . "\n"
. ' DECLARE v_out INT;' . "\n"
. "\n"
. ' ' . "\n"
. ' ' . "\n"
. "\n"
. ' SELECT COUNT(*) INTO v_rentals' . "\n"
. ' FROM rental' . "\n"
. ' WHERE inventory_id = p_inventory_id;' . "\n"
. "\n"
. ' IF v_rentals = 0 THEN' . "\n"
. ' RETURN TRUE;' . "\n"
. ' END IF;' . "\n"
. "\n"
. ' SELECT COUNT(rental_id) INTO v_out' . "\n"
. ' FROM inventory LEFT JOIN rental USING(inventory_id)' . "\n"
. ' WHERE inventory.inventory_id = p_inventory_id' . "\n"
. ' AND rental.return_date IS NULL;' . "\n"
. "\n"
. ' IF v_out > 0 THEN' . "\n"
. ' RETURN FALSE;' . "\n"
. ' ELSE' . "\n"
. ' RETURN TRUE;' . "\n"
. ' END IF;' . "\n"
. 'END',
TokensList::build($stmt->body)
);
}
public function testBuilderTrigger(): void
{
$stmt = new CreateStatement();
$stmt->options = new OptionsArray(['TRIGGER']);
$stmt->name = new Expression('ins_sum');
$stmt->entityOptions = new OptionsArray(['BEFORE', 'INSERT']);
$stmt->table = new Expression('account');
$stmt->body = 'SET @sum = @sum + NEW.amount';
$this->assertEquals(
'CREATE TRIGGER ins_sum BEFORE INSERT ON account ' .
'FOR EACH ROW SET @sum = @sum + NEW.amount',
$stmt->build()
);
}
public function testBuilderRoutine(): void
{
$parser = new Parser(
'DELIMITER $$' . "\n" .
'CREATE FUNCTION test (IN `i` INT) RETURNS VARCHAR ' .
'BEGIN ' .
'DECLARE name VARCHAR DEFAULT ""; ' .
'SELECT name INTO name FROM employees WHERE id = i; ' .
'RETURN name; ' .
'END'
);
$stmt = $parser->statements[0];
$this->assertEquals(
'CREATE FUNCTION test (IN `i` INT) RETURNS VARCHAR ' .
' BEGIN ' .
'DECLARE name VARCHAR DEFAULT ""; ' .
'SELECT name INTO name FROM employees WHERE id = i; ' .
'RETURN name; ' .
'END',
$stmt->build()
);
}
public function testBuildSelect(): void
{
$parser = new Parser('CREATE TABLE new_tbl SELECT * FROM orig_tbl');
$this->assertEquals(
'CREATE TABLE new_tbl SELECT * FROM orig_tbl',
$parser->statements[0]->build()
);
}
public function testBuildCreateTableSortedIndex(): void
{
$parser = new Parser(
<<<'SQL'
CREATE TABLE `entries` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`fk_ug_id` int(11) DEFAULT NULL,
`amount` decimal(10,2) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `entries__ug` (`fk_ug_id` DESC),
KEY `entries__ug2` (`fk_ug_id` ASC),
KEY `33` (`id` ASC, `fk_ug_id` DESC)
) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB AUTO_INCREMENT=4465 DEFAULT CHARSET=utf8
SQL
);
/** @var CreateStatement $stmt */
$stmt = $parser->statements[0];
$tableBody = <<<'SQL'
(
`id` int(11) NOT NULL AUTO_INCREMENT,
`fk_ug_id` int(11) DEFAULT NULL,
`amount` decimal(10,2) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `entries__ug` (`fk_ug_id` DESC),
KEY `entries__ug2` (`fk_ug_id` ASC),
KEY `33` (`id` ASC,`fk_ug_id` DESC)
)
SQL;
$this->assertEquals(
$tableBody,
CreateDefinition::build($stmt->fields)
);
$this->assertEquals(
'CREATE TABLE `entries` '
. $tableBody
. ' ENGINE=InnoDB AUTO_INCREMENT=4465 DEFAULT CHARSET=utf8 TABLESPACE `innodb_system`',
$stmt->build()
);
}
public function testBuildCreateTableComplexIndexes(): void
{
// phpcs:disable Generic.Files.LineLength.TooLong
$parser = new Parser(
<<<'SQL'
CREATE TABLE `page_rebuild_control` (
`proc_row_number` int DEFAULT NULL,
`place_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`place_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`place_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`waterway_id` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`cache_updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`place_active` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`alias_type` int NOT NULL DEFAULT '0',
`status` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`time_taken` float DEFAULT NULL,
PRIMARY KEY (`place_id`,`place_type`) USING BTREE,
KEY `place_type_idx` (`place_type`(10)),
KEY `cached_time_idx` (`cache_updated`),
KEY `active_idx` (`place_active`),
KEY `status_idx` (`status`),
KEY `waterway_idx` (`waterway_id`),
KEY `time_taken_idx` (`time_taken`),
KEY `updated_tz_ind3` (
-- my expression
(convert_tz(`cache_updated`,_utf8mb4'GMT',_utf8mb4'GB'))
) COMMENT 'foo\'s',
KEY `updated_tz_ind_two_indexes_commented` (
-- first expression
(
convert_tz(`cache_updated`,_utf8mb4'GMT',_utf8mb4'GB')
)
,
-- second expression
(
convert_tz(`cache_updated`,_utf8mb4'GMT',_utf8mb4'FR')
)
)
-- and now some options
COMMENT 'haha, this is a complex and indented case',
KEY `alias_type_idx` (`alias_type`),
KEY `updated_tz_ind2` ((convert_tz(`cache_updated`,_utf8mb4'GMT',_utf8mb4'GB'))) COMMENT 'foo\'s',
KEY `updated_tz_ind_two_indexes` ((convert_tz(`cache_updated`,_utf8mb4'GMT',_utf8mb4'GB')), (convert_tz(`cache_updated`,_utf8mb4'GMT',_utf8mb4'FR'))) COMMENT 'bar\'s',
KEY `updated_tz_ind` ((convert_tz(`cache_updated`,_utf8mb4'GMT',_utf8mb4'GB')))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
SQL
);
// phpcs:enable
/** @var CreateStatement $stmt */
$stmt = $parser->statements[0];
// phpcs:disable Generic.Files.LineLength.TooLong
$tableBody = <<<'SQL'
(
`proc_row_number` int DEFAULT NULL,
`place_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`place_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`place_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`waterway_id` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`cache_updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`place_active` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`alias_type` int NOT NULL DEFAULT '0',
`status` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`time_taken` float DEFAULT NULL,
PRIMARY KEY (`place_id`,`place_type`) USING BTREE,
KEY `place_type_idx` (`place_type`(10)),
KEY `cached_time_idx` (`cache_updated`),
KEY `active_idx` (`place_active`),
KEY `status_idx` (`status`),
KEY `waterway_idx` (`waterway_id`),
KEY `time_taken_idx` (`time_taken`),
KEY `updated_tz_ind3` ((convert_tz(`cache_updated`,_utf8mb4'GMT',_utf8mb4'GB'))) COMMENT 'foo\'s',
KEY `updated_tz_ind_two_indexes_commented` ((
convert_tz(`cache_updated`,_utf8mb4'GMT',_utf8mb4'GB')
), (
convert_tz(`cache_updated`,_utf8mb4'GMT',_utf8mb4'FR')
)) COMMENT 'haha, this is a complex and indented case',
KEY `alias_type_idx` (`alias_type`),
KEY `updated_tz_ind2` ((convert_tz(`cache_updated`,_utf8mb4'GMT',_utf8mb4'GB'))) COMMENT 'foo\'s',
KEY `updated_tz_ind_two_indexes` ((convert_tz(`cache_updated`,_utf8mb4'GMT',_utf8mb4'GB')), (convert_tz(`cache_updated`,_utf8mb4'GMT',_utf8mb4'FR'))) COMMENT 'bar\'s',
KEY `updated_tz_ind` ((convert_tz(`cache_updated`,_utf8mb4'GMT',_utf8mb4'GB')))
)
SQL;
// phpcs:enable
$this->assertEquals(
$tableBody,
CreateDefinition::build($stmt->fields)
);
$this->assertEquals(
'CREATE TABLE `page_rebuild_control` '
. $tableBody
. ' ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci',
$stmt->build()
);
}
}
@@ -0,0 +1,99 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Builder;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class DeleteStatementTest extends TestCase
{
public function testBuilderSingleTable(): void
{
/* Assertion 1 */
$query = 'DELETE IGNORE FROM t1';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals($query, $stmt->build());
/* Assertion 2 */
$query = 'DELETE IGNORE FROM t1 WHERE 1=1';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals($query, $stmt->build());
/* Assertion 3 */
$query = 'DELETE IGNORE FROM t1 WHERE 1=1 ORDER BY id ASC';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals($query, $stmt->build());
/* Assertion 4 */
$query = 'DELETE IGNORE FROM t1 WHERE 1=1 ORDER BY id ASC LIMIT 0, 25';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals($query, $stmt->build());
/* Assertion 5 */
$query = 'DELETE IGNORE FROM t1';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals($query, $stmt->build());
/* Assertion 6 */
$query = 'DELETE LOW_PRIORITY FROM `test`.users '
. 'WHERE `id`<3 AND (username="Dan" OR username="Paul") ORDER BY id ASC';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals($query, $stmt->build());
}
public function testBuilderMultiTable(): void
{
/* Assertion 1 */
$query = 'DELETE QUICK table1, table2.* FROM table1 AS `t1`, table2 AS `t2`';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals($query, $stmt->build());
/* Assertion 2 */
$query = 'DELETE QUICK table1, table2.* FROM table1 AS `t1`, table2 AS `t2` WHERE 1=1';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals($query, $stmt->build());
/* Assertion 3 */
$query = 'DELETE QUICK FROM table1, table2.* USING table1 AS `t1`, table2 AS `t2` WHERE 1=1';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals($query, $stmt->build());
/* Assertion 4 */
$query = 'DELETE LOW_PRIORITY t1, t2 FROM t1 INNER JOIN t2 '
. 'INNER JOIN t3 WHERE t1.id=t2.id AND t2.id=t3.id';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals($query, $stmt->build());
}
}
@@ -0,0 +1,104 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Builder;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class ExplainStatementTest extends TestCase
{
public function testBuilder(): void
{
/* Assertion 1 */
$query = 'EXPLAIN SELECT * FROM test;';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
'EXPLAIN SELECT * FROM test',
$stmt->build()
);
/* Assertion 2 */
$query = 'EXPLAIN ANALYZE SELECT * FROM tablename;';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
'EXPLAIN ANALYZE SELECT * FROM tablename',
$stmt->build()
);
/* Assertion 3 */
$query = 'DESC ANALYZE SELECT * FROM tablename;';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
'DESC ANALYZE SELECT * FROM tablename',
$stmt->build()
);
/* Assertion 4 */
$query = 'ANALYZE SELECT * FROM tablename;';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
'ANALYZE SELECT * FROM tablename',
$stmt->build()
);
/* Assertion 5 */
$query = 'DESCRIBE tablename;';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
'DESCRIBE `tablename`',
$stmt->build()
);
/* Assertion 6 */
$query = 'DESC FOR CONNECTION 458';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
'DESC FOR CONNECTION 458',
$stmt->build()
);
/* Assertion 7 */
$query = 'EXPLAIN FORMAT=TREE SELECT * FROM db;';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
'EXPLAIN FORMAT=TREE SELECT * FROM db',
$stmt->build()
);
/* Assertion 8 */
$query = 'DESCRIBE tablename colname;';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
'DESCRIBE `tablename` `colname`',
$stmt->build()
);
/* Assertion 9 */
$query = 'DESCRIBE tablename \'col%me\';';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
'DESCRIBE `tablename` `col%me`',
$stmt->build()
);
/* Assertion 9 */
$query = 'DESCRIBE db.tablename \'col%me\';';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
'DESCRIBE `db`.`tablename` `col%me`',
$stmt->build()
);
}
}
@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Builder;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class InsertStatementTest extends TestCase
{
public function testBuilder(): void
{
/* Assertion 1 */
$parser = new Parser('INSERT INTO tbl(`col1`, `col2`, `col3`) VALUES (1, "str", 3.14)');
$stmt = $parser->statements[0];
$this->assertEquals(
'INSERT INTO tbl(`col1`, `col2`, `col3`) VALUES (1, "str", 3.14)',
$stmt->build()
);
/* Assertion 2 */
/* Reserved keywords (with backquotes as field name) */
$parser = new Parser('INSERT INTO tbl(`order`) VALUES (1)');
$stmt = $parser->statements[0];
$this->assertEquals(
'INSERT INTO tbl(`order`) VALUES (1)',
$stmt->build()
);
/* Assertion 3 */
/* INSERT ... SET ... */
$parser = new Parser('INSERT INTO tbl SET FOO = 1');
$stmt = $parser->statements[0];
$this->assertEquals(
'INSERT INTO tbl SET FOO = 1',
$stmt->build()
);
/* Assertion 4 */
/* INSERT ... SELECT ... */
$parser = new Parser('INSERT INTO tbl SELECT * FROM bar');
$stmt = $parser->statements[0];
$this->assertEquals(
'INSERT INTO tbl SELECT * FROM bar',
$stmt->build()
);
/* Assertion 5 */
/* INSERT ... ON DUPLICATE KEY UPDATE ... */
$parser = new Parser('INSERT INTO tbl SELECT * FROM bar ON DUPLICATE KEY UPDATE baz = 1');
$stmt = $parser->statements[0];
$this->assertEquals(
'INSERT INTO tbl SELECT * FROM bar ON DUPLICATE KEY UPDATE baz = 1',
$stmt->build()
);
/* Assertion 6 */
/* INSERT [OPTIONS] INTO ... */
$parser = new Parser('INSERT DELAYED IGNORE INTO tbl SELECT * FROM bar');
$stmt = $parser->statements[0];
$this->assertEquals(
'INSERT DELAYED IGNORE INTO tbl SELECT * FROM bar',
$stmt->build()
);
}
}
@@ -0,0 +1,108 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Builder;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class LoadStatementTest extends TestCase
{
public function testBuilder(): void
{
/* Assertion 1 */
$query = 'LOAD DATA CONCURRENT INFILE '
. '\'employee1.txt\' INTO TABLE employee';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
'LOAD DATA CONCURRENT INFILE '
. '\'employee1.txt\' INTO TABLE employee',
$stmt->build()
);
/* Assertion 2 */
$query = 'LOAD DATA INFILE \'/tmp/test.txt\' '
. 'INTO TABLE test FIELDS TERMINATED BY '
. '\',\' IGNORE 1 LINES';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
'LOAD DATA INFILE \'/tmp/test.txt\' '
. 'INTO TABLE test FIELDS TERMINATED BY '
. '\',\' IGNORE 1 LINES',
$stmt->build()
);
/* Assertion 3 */
$query = 'LOAD DATA INFILE \'employee3.txt\' '
. 'INTO TABLE employee FIELDS TERMINATED BY '
. '\',\' ENCLOSED BY \'"\'';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
'LOAD DATA INFILE \'employee3.txt\' '
. 'INTO TABLE employee FIELDS TERMINATED BY '
. '\',\' ENCLOSED BY \'"\'',
$stmt->build()
);
/* Assertion 4 */
$query = 'LOAD DATA INFILE \'/tmp/test.txt\' IGNORE '
. 'INTO TABLE test '
. 'CHARACTER SET \'utf8\' '
. 'COLUMNS TERMINATED BY \',\' '
. 'LINES TERMINATED BY \';\' '
. 'IGNORE 1 LINES (col1, col2) SET @a = 1';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
'LOAD DATA INFILE \'/tmp/test.txt\' IGNORE '
. 'INTO TABLE test '
. 'CHARACTER SET \'utf8\' '
. 'COLUMNS TERMINATED BY \',\' '
. 'LINES TERMINATED BY \';\' '
. 'IGNORE 1 LINES (col1, col2) SET @a = 1',
$stmt->build()
);
/* Assertion 5 */
$query = 'LOAD DATA INFILE \'/tmp/test.txt\' REPLACE '
. 'INTO TABLE test COLUMNS TERMINATED BY \',\' IGNORE 1 ROWS';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
'LOAD DATA INFILE \'/tmp/test.txt\' REPLACE '
. 'INTO TABLE test COLUMNS TERMINATED BY \',\' IGNORE 1 ROWS',
$stmt->build()
);
/* Assertion 6 */
$query = 'LOAD DATA INFILE \'/tmp/test.txt\' IGNORE '
. 'INTO TABLE test PARTITION (p0, p1, p2) CHARACTER SET \'utf8\' '
. 'COLUMNS TERMINATED BY \',\' LINES TERMINATED BY \';\' '
. 'IGNORE 1 LINES (col1, col2) SET @a = 1';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
'LOAD DATA INFILE \'/tmp/test.txt\' IGNORE '
. 'INTO TABLE test PARTITION (p0, p1, p2) CHARACTER SET \'utf8\' '
. 'COLUMNS TERMINATED BY \',\' LINES TERMINATED BY \';\' '
. 'IGNORE 1 LINES (col1, col2) SET @a = 1',
$stmt->build()
);
}
}
@@ -0,0 +1,70 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Builder;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class LockStatementTest extends TestCase
{
public function testBuilder(): void
{
/* Assertion 1 */
$query = 'LOCK TABLES table1 AS `t1` READ LOCAL';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals($query, $stmt->build());
/* Assertion 2 */
$query = 'LOCK TABLES table1 AS `t1` READ';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals($query, $stmt->build());
/* Assertion 3 */
$query = 'LOCK TABLES table1 AS `t1` LOW_PRIORITY WRITE';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals($query, $stmt->build());
/* Assertion 4 */
$query = 'LOCK TABLES table1 AS `t1` WRITE';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals($query, $stmt->build());
/* Assertion 5 */
$query = 'LOCK TABLES table1 AS `t1` READ LOCAL, table2 AS `t2` WRITE';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals($query, $stmt->build());
/* Assertion 6 */
$query = 'LOCK TABLES table1 READ LOCAL, table2 AS `t2` WRITE';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals($query, $stmt->build());
/* Assertion 7 */
$query = 'UNLOCK TABLES';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals($query, $stmt->build());
}
}
@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Builder;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class PurgeStatementTest extends TestCase
{
public function testBuilder(): void
{
$query = 'PURGE BINARY LOGS TO \'mysql-bin.010\'';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals($query, $stmt->build());
$query = 'PURGE BINARY LOGS BEFORE \'2008-04-02 22:46:26\'';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals($query, $stmt->build());
}
}
@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Builder;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class RenameStatementTest extends TestCase
{
public function testBuilder(): void
{
$query = 'RENAME TABLE old_table TO new_table';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
$query,
$stmt->build()
);
$query = 'RENAME TABLE current_db.tbl_name TO other_db.tbl_name';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
$query,
$stmt->build()
);
$query = 'RENAME TABLE old_table1 TO new_table1, old_table2 TO new_table2, old_table3 TO new_table3';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
$query,
$stmt->build()
);
}
}
@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Builder;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class ReplaceStatementTest extends TestCase
{
public function testBuilder(): void
{
$parser = new Parser('REPLACE INTO tbl(col1, col2, col3) VALUES (1, "str", 3.14)');
$stmt = $parser->statements[0];
$this->assertEquals(
'REPLACE INTO tbl(`col1`, `col2`, `col3`) VALUES (1, "str", 3.14)',
$stmt->build()
);
}
public function testBuilderSet(): void
{
$parser = new Parser('REPLACE INTO tbl(col1, col2, col3) SET col1=1, col2="str", col3=3.14');
$stmt = $parser->statements[0];
$this->assertEquals(
'REPLACE INTO tbl(`col1`, `col2`, `col3`) SET col1 = 1, col2 = "str", col3 = 3.14',
$stmt->build()
);
}
public function testBuilderSelect(): void
{
$parser = new Parser('REPLACE INTO tbl(col1, col2, col3) SELECT col1, col2, col3 FROM tbl2');
$stmt = $parser->statements[0];
$this->assertEquals(
'REPLACE INTO tbl(`col1`, `col2`, `col3`) SELECT col1, col2, col3 FROM tbl2',
$stmt->build()
);
}
public function testBuilderSelectDelayed(): void
{
$parser = new Parser('REPLACE DELAYED INTO tbl(col1, col2, col3) SELECT col1, col2, col3 FROM tbl2');
$stmt = $parser->statements[0];
$this->assertEquals(
'REPLACE DELAYED INTO tbl(`col1`, `col2`, `col3`) SELECT col1, col2, col3 FROM tbl2',
$stmt->build()
);
}
}
@@ -0,0 +1,356 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Builder;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class SelectStatementTest extends TestCase
{
public function testBuilder(): void
{
$query = 'SELECT * FROM t1 LEFT JOIN (t2, t3, t4) '
. 'ON (t2.a=t1.a AND t3.b=t1.b AND t4.c=t1.c)';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
'SELECT * FROM t1 LEFT JOIN (t2, t3, t4) '
. 'ON (t2.a=t1.a AND t3.b=t1.b AND t4.c=t1.c)',
$stmt->build()
);
$parser = new Parser('SELECT NULL IS NULL');
$stmt = $parser->statements[0];
$this->assertEquals('SELECT NULL IS NULL', $stmt->build());
$parser = new Parser('SELECT NOT 1');
$stmt = $parser->statements[0];
$this->assertEquals('SELECT NOT 1', $stmt->build());
$parser = new Parser('SELECT 1 BETWEEN 0 AND 2');
$stmt = $parser->statements[0];
$this->assertEquals('SELECT 1 BETWEEN 0 AND 2', $stmt->build());
$parser = new Parser("SELECT 'a' NOT REGEXP '^[a-d]'");
$stmt = $parser->statements[0];
$this->assertEquals("SELECT 'a' NOT REGEXP '^[a-d]'", $stmt->build());
$parser = new Parser("SELECT 'a' RLIKE 'a'");
$stmt = $parser->statements[0];
$this->assertEquals("SELECT 'a' RLIKE 'a'", $stmt->build());
}
public function testBuilderUnion(): void
{
$parser = new Parser('SELECT 1 UNION SELECT 2');
$stmt = $parser->statements[0];
$this->assertEquals(
'SELECT 1 UNION SELECT 2',
$stmt->build()
);
}
public function testBuilderWithIsNull(): void
{
$parser = new Parser('SELECT `test3`.`t1` is not null AS `is_not_null` FROM `test3` ;');
$stmt = $parser->statements[0];
$this->assertEquals('SELECT `test3`.`t1` is not null AS `is_not_null` FROM `test3`', $stmt->build());
$parser = new Parser('SELECT test3.t1 is null AS `col1` FROM test3');
$stmt = $parser->statements[0];
$this->assertEquals('SELECT test3.t1 is null AS `col1` FROM test3', $stmt->build());
}
public function testBuilderOrderByNull(): void
{
$query = 'SELECT * FROM some_table ORDER BY some_col IS NULL DESC;';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals('SELECT * FROM some_table ORDER BY some_col IS NULL DESC', $stmt->build());
$query = 'SELECT * FROM some_table ORDER BY some_col IS NOT NULL;';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals('SELECT * FROM some_table ORDER BY some_col IS NOT NULL ASC', $stmt->build());
}
public function testBuilderAlias(): void
{
$parser = new Parser(
'SELECT sgu.id, sgu.email_address FROM `sf_guard_user` sgu '
. 'RIGHT JOIN `student_course_booking` scb ON sgu.id = scb.user_id '
. 'WHERE `has_found_course` = \'1\' GROUP BY sgu.id '
. 'ORDER BY scb.id DESC LIMIT 0,300'
);
$stmt = $parser->statements[0];
$this->assertEquals(
'SELECT sgu.id, sgu.email_address FROM `sf_guard_user` AS `sgu` '
. 'RIGHT JOIN `student_course_booking` AS `scb` ON sgu.id = scb.user_id '
. 'WHERE `has_found_course` = \'1\' GROUP BY sgu.id '
. 'ORDER BY scb.id DESC LIMIT 0, 300',
$stmt->build()
);
}
public function testBuilderAliasOrder(): void
{
$parser = new Parser(
'SELECT sgu.id, sgu.email_address FROM `sf_guard_user` sgu '
. 'RIGHT JOIN `student_course_booking` scb ON sgu.id = scb.user_id '
. 'WHERE `has_found_course` = \'1\' GROUP BY sgu.id '
. 'ORDER BY scb.id LIMIT 0,300'
);
$stmt = $parser->statements[0];
$this->assertEquals(
'SELECT sgu.id, sgu.email_address FROM `sf_guard_user` AS `sgu` '
. 'RIGHT JOIN `student_course_booking` AS `scb` ON sgu.id = scb.user_id '
. 'WHERE `has_found_course` = \'1\' GROUP BY sgu.id '
. 'ORDER BY scb.id ASC LIMIT 0, 300',
$stmt->build()
);
}
public function testBuilderAliasOrderMultiple(): void
{
$parser = new Parser(
'SELECT sgu.id, sgu.email_address FROM `sf_guard_user` sgu '
. 'RIGHT JOIN `student_course_booking` scb ON sgu.id = scb.user_id '
. 'WHERE `has_found_course` = \'1\' GROUP BY sgu.id '
. 'ORDER BY scb.id DESC, scb.order LIMIT 0,300'
);
$stmt = $parser->statements[0];
$this->assertEquals(
'SELECT sgu.id, sgu.email_address FROM `sf_guard_user` AS `sgu` '
. 'RIGHT JOIN `student_course_booking` AS `scb` ON sgu.id = scb.user_id '
. 'WHERE `has_found_course` = \'1\' GROUP BY sgu.id '
. 'ORDER BY scb.id DESC, scb.order ASC LIMIT 0, 300',
$stmt->build()
);
}
public function testBuilderAliasOrderMultipleFunctions(): void
{
$parser = new Parser(
'SELECT sgu.id, sgu.email_address FROM `sf_guard_user` sgu '
. 'RIGHT JOIN `student_course_booking` scb ON sgu.id = scb.user_id '
. 'WHERE `has_found_course` = \'1\' GROUP BY sgu.id '
. 'ORDER BY scb.id DESC, YEAR(scb.dob) LIMIT 0,300'
);
$stmt = $parser->statements[0];
$this->assertEquals(
'SELECT sgu.id, sgu.email_address FROM `sf_guard_user` AS `sgu` '
. 'RIGHT JOIN `student_course_booking` AS `scb` ON sgu.id = scb.user_id '
. 'WHERE `has_found_course` = \'1\' GROUP BY sgu.id '
. 'ORDER BY scb.id DESC, YEAR(scb.dob) ASC LIMIT 0, 300',
$stmt->build()
);
}
public function testBuilderAliasGroupByMultipleFunctions(): void
{
$parser = new Parser(
'SELECT sgu.id, sgu.email_address FROM `sf_guard_user` sgu '
. 'RIGHT JOIN `student_course_booking` scb ON sgu.id = scb.user_id '
. 'WHERE `has_found_course` = \'1\' '
. 'GROUP BY scb.id, YEAR(scb.dob) LIMIT 0,300'
);
$stmt = $parser->statements[0];
$this->assertEquals(
'SELECT sgu.id, sgu.email_address FROM `sf_guard_user` AS `sgu` '
. 'RIGHT JOIN `student_course_booking` AS `scb` ON sgu.id = scb.user_id '
. 'WHERE `has_found_course` = \'1\' '
. 'GROUP BY scb.id, YEAR(scb.dob) LIMIT 0, 300',
$stmt->build()
);
}
public function testBuilderAliasGroupByMultipleFunctionsOrderRemoved(): void
{
$parser = new Parser(
'SELECT sgu.id, sgu.email_address FROM `sf_guard_user` sgu '
. 'RIGHT JOIN `student_course_booking` scb ON sgu.id = scb.user_id '
. 'WHERE `has_found_course` = \'1\' '
. 'GROUP BY scb.id ASC, YEAR(scb.dob) DESC LIMIT 0,300'
);
$stmt = $parser->statements[0];
// The order is not kept, is this an expected behavior ?
// Ref: 4af06d24b041e499fb0e75ab3a98caf9a91700ef
// Issue: #154
$this->assertEquals(
'SELECT sgu.id, sgu.email_address FROM `sf_guard_user` AS `sgu` '
. 'RIGHT JOIN `student_course_booking` AS `scb` ON sgu.id = scb.user_id '
. 'WHERE `has_found_course` = \'1\' '
. 'GROUP BY scb.id, YEAR(scb.dob) LIMIT 0, 300',
$stmt->build()
);
}
public function testBuilderAliasOrderCase(): void
{
$parser = new Parser(
'SELECT * FROM `world_borders` ORDER BY CASE '
. 'WHEN REGION = 2 THEN 99 '
. 'WHEN REGION > 3 THEN REGION+1 '
. 'ELSE 100 END LIMIT 0,300'
);
$stmt = $parser->statements[0];
$this->assertEquals(
'SELECT * FROM `world_borders` ORDER BY CASE '
. 'WHEN REGION = 2 THEN 99 '
. 'WHEN REGION > 3 THEN REGION+1 '
. 'ELSE 100 END ASC LIMIT 0, 300',
$stmt->build()
);
}
public function testBuilderAliasGroupByCase(): void
{
$parser = new Parser(
'SELECT * FROM `world_borders` GROUP BY CASE '
. 'WHEN REGION = 2 THEN 99 '
. 'WHEN REGION > 3 THEN REGION+1 '
. 'ELSE 100 END LIMIT 0,300'
);
$stmt = $parser->statements[0];
$this->assertEquals(
'SELECT * FROM `world_borders` GROUP BY CASE '
. 'WHEN REGION = 2 THEN 99 '
. 'WHEN REGION > 3 THEN REGION+1 '
. 'ELSE 100 END LIMIT 0, 300',
$stmt->build()
);
}
public function testBuilderEndOptions(): void
{
/* Assertion 1 */
$query = 'SELECT pid, name2 FROM tablename WHERE pid = 20 FOR UPDATE';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
$query,
$stmt->build()
);
/* Assertion 2 */
$query = 'SELECT pid, name2 FROM tablename WHERE pid = 20 LOCK IN SHARE MODE';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
$query,
$stmt->build()
);
}
public function testBuilderIntoOptions(): void
{
/* Assertion 1 */
$query = 'SELECT a, b, a+b INTO OUTFILE "/tmp/result.txt"'
. ' COLUMNS TERMINATED BY \',\' OPTIONALLY ENCLOSED BY \'"\''
. ' LINES TERMINATED BY \'\n\''
. ' FROM test_table';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
$query,
$stmt->build()
);
}
public function testBuilderGroupBy(): void
{
$query = 'SELECT COUNT(CustomerID), Country FROM Customers GROUP BY Country';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
$query,
$stmt->build()
);
}
public function testBuilderGroupByWithRollup(): void
{
$query = 'SELECT year FROM movies GROUP BY year WITH ROLLUP';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
$query,
$stmt->build()
);
}
public function testBuilderGroupByMultipleColumnsWithRollup(): void
{
$query = 'SELECT title, year FROM movies GROUP BY title, year WITH ROLLUP';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
$query,
$stmt->build()
);
}
public function testBuilderGroupByWithRollupWithOtherClauses(): void
{
$query = 'SELECT year FROM movies GROUP BY year WITH ROLLUP ORDER BY year ASC LIMIT 0, 5';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
$query,
$stmt->build()
);
}
public function testBuilderIndexHint(): void
{
$query = 'SELECT * FROM address FORCE INDEX (idx_fk_city_id) IGNORE KEY FOR GROUP BY (a, b,c) WHERE city_id<0';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
$query,
$stmt->build()
);
}
public function testBuilderSurroundedByParanthesisWithLimit(): void
{
$query = '(SELECT first_name FROM `actor` LIMIT 1, 2)';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
'SELECT first_name FROM `actor` LIMIT 1, 2',
$stmt->build()
);
}
}
@@ -0,0 +1,179 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Builder;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class SetStatementTest extends TestCase
{
public function testBuilderView(): void
{
/* Assertion 1 */
$query = 'SET CHARACTER SET \'utf8\'';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
$query,
$stmt->build()
);
/* Assertion 2 */
$query = 'SET CHARSET \'utf8\'';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
$query,
$stmt->build()
);
/* Assertion 3 */
$query = 'SET NAMES \'utf8\'';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
$query,
$stmt->build()
);
/* Assertion 4 */
$query = 'SET NAMES \'utf8\' COLLATE \'utf8_general_ci\'';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
'SET NAMES \'utf8\' COLLATE \'utf8_general_ci\'',
$stmt->build()
);
/* Assertion 5 */
$query = 'SET NAMES \'utf8\' DEFAULT';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
'SET NAMES \'utf8\' DEFAULT',
$stmt->build()
);
/* Assertion 6 */
$query = 'SET sql_mode = \'TRADITIONAL\'';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
'SET sql_mode = \'TRADITIONAL\'',
$stmt->build()
);
/* Assertion 7 */
$query = 'SET SESSION sql_mode = \'TRADITIONAL\'';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
'SET SESSION sql_mode = \'TRADITIONAL\'',
$stmt->build()
);
/* Assertion 8 */
$query = 'SET GLOBAL sql_mode = \'TRADITIONAL\'';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
'SET GLOBAL sql_mode = \'TRADITIONAL\'',
$stmt->build()
);
/* Assertion 9 */
$query = 'SET @@SESSION sql_mode = \'TRADITIONAL\'';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
'SET SESSION sql_mode = \'TRADITIONAL\'',
$stmt->build()
);
/* Assertion 10 */
$query = 'SET @@GLOBAL sql_mode = \'TRADITIONAL\'';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
'SET GLOBAL sql_mode = \'TRADITIONAL\'',
$stmt->build()
);
/* Assertion 11 */
$query = 'SET @@sql_mode = \'TRADITIONAL\'';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
'SET @@sql_mode = \'TRADITIONAL\'',
$stmt->build()
);
/* Assertion 12 */
$query = 'SET PERSIST sql_mode = \'TRADITIONAL\'';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
'SET PERSIST sql_mode = \'TRADITIONAL\'',
$stmt->build()
);
/* Assertion 13 */
$query = 'SET PERSIST_ONLY sql_mode = \'TRADITIONAL\'';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
'SET PERSIST_ONLY sql_mode = \'TRADITIONAL\'',
$stmt->build()
);
/* Assertion 14 */
$query = 'SET @@PERSIST sql_mode = \'TRADITIONAL\'';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
'SET PERSIST sql_mode = \'TRADITIONAL\'',
$stmt->build()
);
/* Assertion 15 */
$query = 'SET @@PERSIST_ONLY sql_mode = \'TRADITIONAL\'';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
'SET PERSIST_ONLY sql_mode = \'TRADITIONAL\'',
$stmt->build()
);
}
}
@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Builder;
use PhpMyAdmin\SqlParser\Components\Condition;
use PhpMyAdmin\SqlParser\Components\Expression;
use PhpMyAdmin\SqlParser\Components\Limit;
use PhpMyAdmin\SqlParser\Components\OptionsArray;
use PhpMyAdmin\SqlParser\Statements\SelectStatement;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class StatementTest extends TestCase
{
public function testBuilder(): void
{
$stmt = new SelectStatement();
$stmt->options = new OptionsArray(['DISTINCT']);
$stmt->expr[] = new Expression('sakila', 'film', 'film_id', 'fid');
$stmt->expr[] = new Expression('COUNT(film_id)');
$stmt->from[] = new Expression('', 'film', '');
$stmt->from[] = new Expression('', 'actor', '');
$stmt->where[] = new Condition('film_id > 10');
$stmt->where[] = new Condition('OR');
$stmt->where[] = new Condition('actor.age > 25');
$stmt->limit = new Limit(1, 10);
$this->assertEquals(
'SELECT DISTINCT `sakila`.`film`.`film_id` AS `fid`, COUNT(film_id) ' .
'FROM `film`, `actor` ' .
'WHERE film_id > 10 OR actor.age > 25 ' .
'LIMIT 10, 1',
(string) $stmt
);
}
}
@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Builder;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class TransactionStatementTest extends TestCase
{
public function testBuilder(): void
{
$query = 'START TRANSACTION;' .
'SELECT @A:=SUM(salary) FROM table1 WHERE type=1;' .
'UPDATE table2 SET summary=@A WHERE type=1;' .
'COMMIT;';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
'START TRANSACTION;' .
'SELECT @A:=SUM(salary) FROM table1 WHERE type=1;' .
'UPDATE table2 SET summary = @A WHERE type=1;' .
'COMMIT',
$stmt->build()
);
}
}
@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Builder;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class TruncateStatementTest extends TestCase
{
public function testBuilder(): void
{
$query = 'TRUNCATE TABLE mytable;';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals($query, $stmt->build());
}
public function testBuilderDbtable(): void
{
$query = 'TRUNCATE TABLE mydb.mytable;';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals($query, $stmt->build());
}
public function testBuilderDbtableBackQuotes(): void
{
$query = 'TRUNCATE TABLE `mydb`.`mytable`;';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals($query, $stmt->build());
}
}
@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Builder;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class UpdateStatementTest extends TestCase
{
public function testBuilder(): void
{
/* Assertion 1 */
$parser = new Parser(
'update user u left join user_detail ud on u.id = ud.user_id set ud.ip =\'33\' where u.id = 1'
);
$stmt = $parser->statements[0];
$this->assertEquals(
'UPDATE user AS `u` LEFT JOIN user_detail AS `ud` ON u.id = ud.user_id SET ud.ip = \'33\' WHERE u.id = 1',
$stmt->build()
);
/* Assertion 2 */
$parser = new Parser('update user u join user_detail ud on u.id = ud.user_id set ud.ip =\'33\' where u.id = 1');
$stmt = $parser->statements[0];
$this->assertEquals(
'UPDATE user AS `u` JOIN user_detail AS `ud` ON u.id = ud.user_id SET ud.ip = \'33\' WHERE u.id = 1',
$stmt->build()
);
/* Assertion 3 */
$parser = new Parser(
'update user u inner join user_detail ud on u.id = ud.user_id set ud.ip =\'33\' where u.id = 1'
);
$stmt = $parser->statements[0];
$this->assertEquals(
'UPDATE user AS `u` INNER JOIN user_detail AS `ud` ON u.id = ud.user_id SET ud.ip = \'33\' WHERE u.id = 1',
$stmt->build()
);
}
}
@@ -0,0 +1,94 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Components;
use PhpMyAdmin\SqlParser\Components\Array2d;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class Array2dTest extends TestCase
{
public function testParse(): void
{
$parser = new Parser();
$arrays = Array2d::parse($parser, $this->getTokensList('(1, 2) +'));
$this->assertEquals(
[
1,
2,
],
$arrays[0]->values
);
}
public function testBuild(): void
{
$arrays = Array2d::parse(new Parser(), $this->getTokensList('(1, 2), (3, 4), (5, 6)'));
$this->assertEquals(
'(1, 2), (3, 4), (5, 6)',
Array2d::build($arrays)
);
}
public function testParseErr1(): void
{
$parser = new Parser();
Array2d::parse($parser, $this->getTokensList('(1, 2 +'));
$this->assertCount(1, $parser->errors);
$this->assertEquals('A closing bracket was expected.', $parser->errors[0]->getMessage());
}
public function testParseErr2(): void
{
$parser = new Parser();
Array2d::parse($parser, $this->getTokensList('(1, 2 TABLE'));
$this->assertCount(1, $parser->errors);
$this->assertEquals('A closing bracket was expected.', $parser->errors[0]->getMessage());
}
public function testParseErr3(): void
{
$parser = new Parser();
Array2d::parse($parser, $this->getTokensList(')'));
$this->assertCount(1, $parser->errors);
$this->assertEquals(
'An opening bracket followed by a set of values was expected.',
$parser->errors[0]->getMessage()
);
}
public function testParseErr4(): void
{
$parser = new Parser();
Array2d::parse($parser, $this->getTokensList('TABLE'));
$this->assertCount(1, $parser->errors);
$this->assertEquals(
'An opening bracket followed by a set of values was expected.',
$parser->errors[0]->getMessage()
);
}
public function testParseErr5(): void
{
$parser = new Parser();
Array2d::parse($parser, $this->getTokensList('(1, 2),'));
$this->assertCount(1, $parser->errors);
$this->assertEquals(
'An opening bracket followed by a set of values was expected.',
$parser->errors[0]->getMessage()
);
}
public function testParseErr6(): void
{
$parser = new Parser();
Array2d::parse($parser, $this->getTokensList('(1, 2),(3)'));
$this->assertCount(1, $parser->errors);
$this->assertEquals(
'2 values were expected, but found 1.',
$parser->errors[0]->getMessage()
);
}
}
@@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Components;
use PhpMyAdmin\SqlParser\Components\ArrayObj;
use PhpMyAdmin\SqlParser\Components\Expression;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class ArrayObjTest extends TestCase
{
public function testBuildRaw(): void
{
$component = new ArrayObj(['a', 'b'], []);
$this->assertEquals('(a, b)', ArrayObj::build($component));
}
public function testBuildValues(): void
{
$component = new ArrayObj([], ['a', 'b']);
$this->assertEquals('(a, b)', ArrayObj::build($component));
}
public function testParseType(): void
{
$components = ArrayObj::parse(
new Parser(),
$this->getTokensList('(1 + 2, 3 + 4)'),
[
'type' => Expression::class,
'typeOptions' => ['breakOnParentheses' => true],
]
);
$this->assertInstanceOf(Expression::class, $components[0]);
$this->assertInstanceOf(Expression::class, $components[1]);
$this->assertEquals($components[0]->expr, '1 + 2');
$this->assertEquals($components[1]->expr, '3 + 4');
}
/**
* @dataProvider parseProvider
*/
public function testParse(string $test): void
{
$this->runParserTest($test);
}
/**
* @return string[][]
*/
public static function parseProvider(): array
{
return [
['parser/parseArrayErr1'],
['parser/parseArrayErr3'],
];
}
}
@@ -0,0 +1,128 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Components;
use PhpMyAdmin\SqlParser\Components\CaseExpression;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class CaseExpressionTest extends TestCase
{
public function testParseBuild(): void
{
$caseExprQuery = 'case 1 when 1 then "Some" else "Other" end';
$component = CaseExpression::parse(
new Parser(),
$this->getTokensList($caseExprQuery)
);
$this->assertEquals(
CaseExpression::build($component),
'CASE 1 WHEN 1 THEN "Some" ELSE "Other" END'
);
}
public function testParseBuild2(): void
{
$caseExprQuery = 'case when 1=1 then "India" else "Other" end';
$component = CaseExpression::parse(
new Parser(),
$this->getTokensList($caseExprQuery)
);
$this->assertEquals(
CaseExpression::build($component),
'CASE WHEN 1=1 THEN "India" ELSE "Other" END'
);
}
public function testParseBuild3(): void
{
$caseExprQuery = 'case 1 when 1 then "Some" '
. 'when 2 then "SomeOther" else "Other" end';
$component = CaseExpression::parse(
new Parser(),
$this->getTokensList($caseExprQuery)
);
$this->assertEquals(
CaseExpression::build($component),
'CASE 1 WHEN 1 THEN "Some" WHEN 2 THEN "SomeOther" ELSE "Other" END'
);
}
public function testParseBuild4(): void
{
$caseExprQuery = 'case 1 when 1 then "Some" '
. 'when 2 then "SomeOther" end';
$component = CaseExpression::parse(
new Parser(),
$this->getTokensList($caseExprQuery)
);
$this->assertEquals(
CaseExpression::build($component),
'CASE 1 WHEN 1 THEN "Some" WHEN 2 THEN "SomeOther" END'
);
}
public function testParseBuild5(): void
{
$caseExprQuery = 'case when 1=1 then "Some" '
. 'when 1=2 then "SomeOther" else "Other" end';
$component = CaseExpression::parse(
new Parser(),
$this->getTokensList($caseExprQuery)
);
$this->assertEquals(
CaseExpression::build($component),
'CASE WHEN 1=1 THEN "Some" WHEN 1=2 THEN "SomeOther" ELSE "Other" END'
);
}
public function testParseBuild6(): void
{
$caseExprQuery = 'case when 1=1 then "Some" '
. 'when 1=2 then "SomeOther" end';
$component = CaseExpression::parse(
new Parser(),
$this->getTokensList($caseExprQuery)
);
$this->assertEquals(
CaseExpression::build($component),
'CASE WHEN 1=1 THEN "Some" WHEN 1=2 THEN "SomeOther" END'
);
}
public function testParseBuild7(): void
{
$caseExprQuery = 'case when 1=1 then "Some" '
. 'when 1=2 then "SomeOther" end AS foo';
$component = CaseExpression::parse(
new Parser(),
$this->getTokensList($caseExprQuery)
);
$this->assertEquals(
CaseExpression::build($component),
'CASE WHEN 1=1 THEN "Some" WHEN 1=2 THEN "SomeOther" END AS `foo`'
);
}
public function testParseBuild8(): void
{
$caseExprQuery = 'case when 1=1 then "Some" '
. 'when 1=2 then "SomeOther" end foo';
$component = CaseExpression::parse(
new Parser(),
$this->getTokensList($caseExprQuery)
);
$this->assertEquals(
CaseExpression::build($component),
'CASE WHEN 1=1 THEN "Some" WHEN 1=2 THEN "SomeOther" END AS `foo`'
);
}
public function testBuildWithIncompleteCaseExpression(): void
{
$incompleteCaseExpressionComponent = new CaseExpression();
$this->assertEquals('CASE END', CaseExpression::build($incompleteCaseExpressionComponent));
}
}
@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Components;
use PhpMyAdmin\SqlParser\Component;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
use PhpMyAdmin\SqlParser\TokensList;
use Throwable;
class ComponentTest extends TestCase
{
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testParse(): void
{
$this->expectExceptionMessage('Not implemented yet.');
$this->expectException(Throwable::class);
Component::parse(new Parser(), new TokensList());
}
public function testBuild(): void
{
$this->expectExceptionMessage('Not implemented yet.');
$this->expectException(Throwable::class);
Component::build(null);
}
}
@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Components;
use PhpMyAdmin\SqlParser\Components\Condition;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class ConditionTest extends TestCase
{
public function testParse(): void
{
$component = Condition::parse(new Parser(), $this->getTokensList('/* id = */ id = 10'));
$this->assertEquals($component[0]->expr, 'id = 10');
}
public function testParseBetween(): void
{
$component = Condition::parse(
new Parser(),
$this->getTokensList('(id BETWEEN 10 AND 20) OR (id BETWEEN 30 AND 40)')
);
$this->assertEquals($component[0]->expr, '(id BETWEEN 10 AND 20)');
$this->assertEquals($component[1]->expr, 'OR');
$this->assertEquals($component[2]->expr, '(id BETWEEN 30 AND 40)');
}
public function testParseAnd(): void
{
$component = Condition::parse(
new Parser(),
$this->getTokensList("`col` LIKE 'AND'")
);
$this->assertEquals(
"`col` LIKE 'AND'",
Condition::build($component)
);
}
}
@@ -0,0 +1,162 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Components;
use PhpMyAdmin\SqlParser\Components\CreateDefinition;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Statements\CreateStatement;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class CreateDefinitionTest extends TestCase
{
public function testParse(): void
{
$component = CreateDefinition::parse(
new Parser(),
$this->getTokensList('(str TEXT, FULLTEXT INDEX indx (str))')
);
$this->assertEquals('str', $component[0]->name);
$this->assertEquals('FULLTEXT INDEX', $component[1]->key->type);
$this->assertEquals('indx', $component[1]->key->name);
$this->assertEquals('FULLTEXT INDEX `indx` (`str`)', (string) $component[1]);
}
public function testParse2(): void
{
$component = CreateDefinition::parse(
new Parser(),
$this->getTokensList('(str TEXT NOT NULL INVISIBLE)')
);
$this->assertEquals('str', $component[0]->name);
$this->assertEquals('TEXT', $component[0]->type->name);
$this->assertTrue($component[0]->options->has('INVISIBLE'));
$this->assertTrue($component[0]->options->has('NOT NULL'));
}
public function testParseErr1(): void
{
$parser = new Parser();
$component = CreateDefinition::parse(
$parser,
$this->getTokensList('(str TEXT, FULLTEXT INDEX indx (str)')
);
$this->assertCount(2, $component);
$this->assertEquals(
'A closing bracket was expected.',
$parser->errors[0]->getMessage()
);
}
public function testParseErr2(): void
{
$parser = new Parser();
CreateDefinition::parse(
$parser,
$this->getTokensList(')')
);
$this->assertEquals(
'An opening bracket was expected.',
$parser->errors[0]->getMessage()
);
}
public function testBuild(): void
{
$parser = new Parser(
'CREATE TABLE `payment` (' .
'-- snippet' . "\n" .
'`customer_id` smallint(5) unsigned NOT NULL,' .
'CONSTRAINT `fk_payment_customer` FOREIGN KEY (`customer_id`) ' .
'REFERENCES `customer` (`customer_id`) ON UPDATE CASCADE' .
') ENGINE=InnoDB"'
);
$this->assertInstanceOf(CreateStatement::class, $parser->statements[0]);
$this->assertEquals(
'CONSTRAINT `fk_payment_customer` FOREIGN KEY (`customer_id`) ' .
'REFERENCES `customer` (`customer_id`) ON UPDATE CASCADE',
CreateDefinition::build($parser->statements[0]->fields[1])
);
}
public function testBuild2(): void
{
$parser = new Parser(
'CREATE TABLE `payment` (' .
'-- snippet' . "\n" .
'`customer_id` smallint(5) unsigned NOT NULL,' .
'`customer_data` longtext CHARACTER SET utf8mb4 CHARSET utf8mb4_bin NOT NULL ' .
'CHECK (json_valid(customer_data)),CONSTRAINT `fk_payment_customer` FOREIGN KEY ' .
'(`customer_id`) REFERENCES `customer` (`customer_id`) ON UPDATE CASCADE' .
') ENGINE=InnoDB"'
);
$this->assertInstanceOf(CreateStatement::class, $parser->statements[0]);
$this->assertEquals(
'CONSTRAINT `fk_payment_customer` FOREIGN KEY (`customer_id`) ' .
'REFERENCES `customer` (`customer_id`) ON UPDATE CASCADE',
CreateDefinition::build($parser->statements[0]->fields[2])
);
}
public function testBuild3(): void
{
$parser = new Parser(
'DROP TABLE IF EXISTS `searches`;'
. 'CREATE TABLE `searches` ('
. ' `id` int(10) unsigned NOT NULL AUTO_INCREMENT,'
. ' `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,'
. ' `public_name` varchar(120) COLLATE utf8_unicode_ci NOT NULL,'
. ' `group_id` smallint(5) unsigned NOT NULL DEFAULT \'0\','
. ' `shortdesc` tinytext COLLATE utf8_unicode_ci,'
. ' `show_separators` tinyint(1) NOT NULL DEFAULT \'0\','
. ' `show_separators_two` tinyint(1) NOT NULL DEFAULT FALSE,'
. ' `deleted` tinyint(1) NOT NULL DEFAULT \'0\','
. ' PRIMARY KEY (`id`)'
. ') ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ;'
. ''
. 'ALTER TABLE `searches` ADD `admins_only` BOOLEAN NOT NULL DEFAULT FALSE AFTER `show_separators`;'
);
$this->assertInstanceOf(CreateStatement::class, $parser->statements[1]);
$this->assertEquals(
'`public_name` varchar(120) COLLATE utf8_unicode_ci NOT NULL',
CreateDefinition::build($parser->statements[1]->fields[2])
);
$this->assertEquals(
'`show_separators` tinyint(1) NOT NULL DEFAULT \'0\'',
CreateDefinition::build($parser->statements[1]->fields[5])
);
$this->assertEquals(
'`show_separators_two` tinyint(1) NOT NULL DEFAULT FALSE',
CreateDefinition::build($parser->statements[1]->fields[6])
);
}
public function testBuildWithInvisibleKeyword(): void
{
$parser = new Parser(
'CREATE TABLE `payment` (' .
'-- snippet' . "\n" .
'`customer_id` smallint(5) unsigned NOT NULL INVISIBLE,' .
'`customer_data` longtext CHARACTER SET utf8mb4 CHARSET utf8mb4_bin NOT NULL ' .
'CHECK (json_valid(customer_data)),CONSTRAINT `fk_payment_customer` FOREIGN KEY ' .
'(`customer_id`) REFERENCES `customer` (`customer_id`) ON UPDATE CASCADE' .
') ENGINE=InnoDB"'
);
$this->assertInstanceOf(CreateStatement::class, $parser->statements[0]);
$this->assertEquals(
'`customer_id` smallint(5) UNSIGNED NOT NULL INVISIBLE',
CreateDefinition::build($parser->statements[0]->fields[0])
);
}
public function testBuildWithCompressed(): void
{
$query = 'CREATE TABLE `user` ( `message2` TEXT COMPRESSED )';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals("CREATE TABLE `user` (\n `message2` text COMPRESSED\n) ", $stmt->build());
}
}
@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Components;
use PhpMyAdmin\SqlParser\Components\ExpressionArray;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class ExpressionArrayTest extends TestCase
{
public function testParse(): void
{
$component = ExpressionArray::parse(
new Parser(),
$this->getTokensList('(expr)'),
['breakOnParentheses' => true]
);
$this->assertEquals([], $component);
}
public function testParse2(): void
{
$component = ExpressionArray::parse(
new Parser(),
$this->getTokensList('(expr) +'),
['parenthesesDelimited' => true]
);
$this->assertCount(1, $component);
$this->assertEquals('(expr)', $component[0]->expr);
}
public function testParseWithCommentsNoOptions(): void
{
$component = ExpressionArray::parse(
new Parser(),
$this->getTokensList('(expr) -- comment ?')
);
$this->assertCount(1, $component);
$this->assertEquals('(expr)', $component[0]->expr);
}
public function testParseWithCommentsAndOptions(): void
{
$component = ExpressionArray::parse(
new Parser(),
$this->getTokensList('(expr -- comment ?)'),
['parenthesesDelimited' => true]
);
$this->assertCount(1, $component);
$this->assertEquals('(expr', $component[0]->expr);
}
}
@@ -0,0 +1,154 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Components;
use PhpMyAdmin\SqlParser\Components\Expression;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class ExpressionTest extends TestCase
{
public function testParse(): void
{
$component = Expression::parse(new Parser(), $this->getTokensList('IF(film_id > 0, film_id, film_id)'));
$this->assertEquals($component->expr, 'IF(film_id > 0, film_id, film_id)');
}
public function testParse2(): void
{
$component = Expression::parse(new Parser(), $this->getTokensList('col`test`'));
$this->assertEquals($component->expr, 'col');
}
public function testParse3(): void
{
$component = Expression::parse(new Parser(), $this->getTokensList('col xx'));
$this->assertEquals($component->alias, 'xx');
$component = Expression::parse(new Parser(), $this->getTokensList('col y'));
$this->assertEquals($component->alias, 'y');
$component = Expression::parse(new Parser(), $this->getTokensList('avg.col FROM (SELECT ev.col FROM ev)'));
$this->assertEquals($component->table, 'avg');
$this->assertEquals($component->expr, 'avg.col');
$component = Expression::parse(new Parser(), $this->getTokensList('x.id FROM (SELECT a.id FROM a) x'));
$this->assertEquals($component->table, 'x');
$this->assertEquals($component->expr, 'x.id');
}
/**
* @dataProvider parseErrProvider
*/
public function testParseErr(string $expr, string $error): void
{
$parser = new Parser();
Expression::parse($parser, $this->getTokensList($expr));
$errors = $this->getErrorsAsArray($parser);
$this->assertEquals($errors[0][0], $error);
}
/**
* @return string[][]
*/
public static function parseErrProvider(): array
{
return [
/*
[
'(1))',
'Unexpected closing bracket.',
],
*/
[
'tbl..col',
'Unexpected dot.',
],
[
'id AS AS id2',
'An alias was expected.',
],
[
'id`id2`\'id3\'',
'An alias was previously found.',
],
[
'(id) id2 id3',
'An alias was previously found.',
],
];
}
public function testBuild(): void
{
$component = [
new Expression('1 + 2', 'three'),
new Expression('1 + 3', 'four'),
];
$this->assertEquals(
Expression::build($component),
'1 + 2 AS `three`, 1 + 3 AS `four`'
);
}
/**
* @return string[][]
*/
public static function mysqlCommandsProvider(): array
{
return [
[
'/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;',
'SET @OLD_CHARACTER_SET_CLIENT = @@CHARACTER_SET_CLIENT',
],
[
'/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;',
'SET @OLD_CHARACTER_SET_RESULTS = @@CHARACTER_SET_RESULTS',
],
[
'/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;',
'SET @OLD_COLLATION_CONNECTION = @@COLLATION_CONNECTION',
],
[
'/*!40101 SET NAMES utf8 */;',
'SET NAMES utf8',
],
[
'/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;',
'SET @OLD_TIME_ZONE = @@TIME_ZONE',
],
[
"/*!40103 SET TIME_ZONE='+00:00' */;",
"SET TIME_ZONE = '+00:00'",
],
[
'/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;',
'SET @OLD_UNIQUE_CHECKS = @@UNIQUE_CHECKS, UNIQUE_CHECKS = 0',
],
[
'/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;',
'SET @OLD_FOREIGN_KEY_CHECKS = @@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS = 0',
],
[
"/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;",
"SET @OLD_SQL_MODE = @@SQL_MODE, SQL_MODE = 'NO_AUTO_VALUE_ON_ZERO'",
],
[
'/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;',
'SET @OLD_SQL_NOTES = @@SQL_NOTES, SQL_NOTES = 0',
],
];
}
/**
* @dataProvider mysqlCommandsProvider
*/
public function testMysqlCommands(string $expr, string $expected): void
{
$parser = new Parser($expr, true);
$parser->parse();
self::assertSame($expected, $parser->statements[0]->build());
}
}
@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Components;
use PhpMyAdmin\SqlParser\Components\ArrayObj;
use PhpMyAdmin\SqlParser\Components\FunctionCall;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class FunctionCallTest extends TestCase
{
public function testBuildArray(): void
{
$component = new FunctionCall('func', ['a', 'b']);
$this->assertEquals('func(a, b)', FunctionCall::build($component));
}
public function testBuildArrayObj(): void
{
$component = new FunctionCall('func', new ArrayObj(['a', 'b']));
$this->assertEquals('func(a, b)', FunctionCall::build($component));
}
}
@@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Components;
use Generator;
use PhpMyAdmin\SqlParser\Components\Expression;
use PhpMyAdmin\SqlParser\Components\GroupKeyword;
use PhpMyAdmin\SqlParser\Tests\TestCase;
use function array_map;
class GroupKeywordTest extends TestCase
{
/**
* @return Generator<string, array{GroupKeyword|array<GroupKeyword>, string}>
*/
public static function provideExpressions(): Generator
{
yield 'With no expression at all' => [[], ''];
yield 'With single simple expression' => [
self::makeComponentFrom('a'),
'a',
];
yield 'With multiple simple expressions' => [
self::makeComponentsFrom('a', 'b', 'c'),
'a, b, c',
];
yield 'With single untrimmed expression' => [
self::makeComponentFrom(' o '),
'o',
];
yield 'With single untrimmed expression having several kinds of whitespaces' => [
self::makeComponentFrom(" \n\r foo \t\v\x00 "),
'foo',
];
yield 'With multiple untrimmed expressions' => [
self::makeComponentsFrom(' x', ' y ', 'z '),
'x, y, z',
];
yield 'With multiple untrimmed expression having several kinds of whitespaces' => [
self::makeComponentsFrom(" \n\r\t\v\x00foo", " \n\r\tbar\v\x00", "baz \n\r\t\v\x00"),
'foo, bar, baz',
];
}
/**
* @param GroupKeyword|array<GroupKeyword> $component
*
* @dataProvider provideExpressions
*/
public function testBuild($component, string $expected): void
{
$this->assertSame($expected, GroupKeyword::build($component));
}
private static function makeComponentFrom(string $string): GroupKeyword
{
return new GroupKeyword(new Expression($string));
}
/**
* @return array<GroupKeyword>
*/
private static function makeComponentsFrom(string ...$string): array
{
return array_map([self::class, 'makeComponentFrom'], $string);
}
}
@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Components;
use PhpMyAdmin\SqlParser\Components\IntoKeyword;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class IntoKeywordTest extends TestCase
{
public function testParse(): void
{
$component = IntoKeyword::parse(new Parser(), $this->getTokensList('OUTFILE "/tmp/outfile.txt"'));
$this->assertEquals($component->type, 'OUTFILE');
$this->assertEquals($component->dest, '/tmp/outfile.txt');
}
public function testBuild(): void
{
$component = IntoKeyword::parse(new Parser(), $this->getTokensList('tbl(`col1`, `col2`)'));
$this->assertEquals('tbl(`col1`, `col2`)', IntoKeyword::build($component));
}
public function testBuildValues(): void
{
$component = IntoKeyword::parse(new Parser(), $this->getTokensList('@a1, @a2, @a3'));
$this->assertEquals('@a1, @a2, @a3', IntoKeyword::build($component));
}
public function testBuildOutfile(): void
{
$component = IntoKeyword::parse(new Parser(), $this->getTokensList('OUTFILE "/tmp/outfile.txt"'));
$this->assertEquals('OUTFILE "/tmp/outfile.txt"', IntoKeyword::build($component));
}
public function testParseErr1(): void
{
$component = IntoKeyword::parse(new Parser(), $this->getTokensList('OUTFILE;'));
$this->assertEquals($component->type, 'OUTFILE');
}
}
@@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Components;
use PhpMyAdmin\SqlParser\Components\JoinKeyword;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class JoinKeywordTest extends TestCase
{
public function testParseIncomplete(): void
{
$component = JoinKeyword::parse(new Parser(), $this->getTokensList('JOIN a'));
$this->assertCount(1, $component);
$this->assertEquals('a', $component[0]->expr->expr);
$this->assertNull($component[0]->on);
$this->assertNull($component[0]->using);
}
public function testParseIncompleteUsing(): void
{
$component = JoinKeyword::parse(new Parser(), $this->getTokensList('JOIN table2 USING (id)'));
$this->assertCount(1, $component);
$this->assertEquals('table2', $component[0]->expr->expr);
$this->assertNull($component[0]->on);
$this->assertEquals(['id'], $component[0]->using->values);
}
public function testBuild(): void
{
$component = JoinKeyword::parse(
new Parser(),
$this->getTokensList(
'LEFT JOIN (t2 CROSS JOIN t3 CROSS JOIN t4) ' .
'ON (t2.a=t1.a AND t3.b=t1.b AND t4.c=t1.c)'
)
);
$this->assertEquals(
'LEFT JOIN (t2 CROSS JOIN t3 CROSS JOIN t4) ' .
'ON (t2.a=t1.a AND t3.b=t1.b AND t4.c=t1.c)',
JoinKeyword::build($component)
);
}
}
@@ -0,0 +1,374 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Components;
use PhpMyAdmin\SqlParser\Components\Expression;
use PhpMyAdmin\SqlParser\Components\Key;
use PhpMyAdmin\SqlParser\Components\OptionsArray;
use PhpMyAdmin\SqlParser\Exceptions\ParserException;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
use PhpMyAdmin\SqlParser\Token;
class KeyTest extends TestCase
{
public function testParse(): void
{
$component = Key::parse(
new Parser(),
$this->getTokensList('')
);
$this->assertNull($component->type);
$this->assertNull($component->options);
$this->assertNull($component->name);
$this->assertNull($component->expr);
$this->assertSame([], $component->columns);
$this->assertSame(
'()',
Key::build($component)
);
}
public function testParseKeyWithoutOptions(): void
{
$component = Key::parse(
new Parser(),
$this->getTokensList('KEY `alias_type_idx` (`alias_type`),')
);
$this->assertEquals('KEY', $component->type);
$this->assertEquals('alias_type_idx', $component->name);
$this->assertEquals(new OptionsArray(), $component->options);
$this->assertNull($component->expr);
$this->assertSame([['name' => 'alias_type']], $component->columns);
$this->assertSame(
'KEY `alias_type_idx` (`alias_type`)',
Key::build($component)
);
}
public function testParseKeyWithLengthWithoutOptions(): void
{
$component = Key::parse(
new Parser(),
$this->getTokensList('KEY `alias_type_idx` (`alias_type`(10)),')
);
$this->assertEquals('KEY', $component->type);
$this->assertEquals('alias_type_idx', $component->name);
$this->assertEquals(new OptionsArray(), $component->options);
$this->assertNull($component->expr);
$this->assertSame([['name' => 'alias_type', 'length' => 10]], $component->columns);
$this->assertSame(
'KEY `alias_type_idx` (`alias_type`(10))',
Key::build($component)
);
}
public function testParseKeyWithLengthWithoutOptionsWithOrder(): void
{
$component = Key::parse(
new Parser(),
$this->getTokensList('KEY `alias_type_idx` (`alias_type`(10) ASC),')
);
$this->assertEquals('KEY', $component->type);
$this->assertEquals('alias_type_idx', $component->name);
$this->assertEquals(new OptionsArray(), $component->options);
$this->assertNull($component->expr);
$this->assertSame([['name' => 'alias_type', 'length' => 10, 'order' => 'ASC']], $component->columns);
$this->assertSame(
'KEY `alias_type_idx` (`alias_type`(10) ASC)',
Key::build($component)
);
}
public function testParseKeyWithoutOptionsWithOrderLowercase(): void
{
$component = Key::parse(
new Parser(),
$this->getTokensList('KEY `alias_type_idx` (`alias_type` desc),')
);
$this->assertEquals('KEY', $component->type);
$this->assertEquals('alias_type_idx', $component->name);
$this->assertEquals(new OptionsArray(), $component->options);
$this->assertNull($component->expr);
$this->assertSame([['name' => 'alias_type', 'order' => 'DESC']], $component->columns);
$this->assertSame(
'KEY `alias_type_idx` (`alias_type` DESC)',
Key::build($component)
);
}
public function testParseKeyWithoutOptionsWithOrder(): void
{
$component = Key::parse(
new Parser(),
$this->getTokensList('KEY `alias_type_idx` (`alias_type` DESC),')
);
$this->assertEquals('KEY', $component->type);
$this->assertEquals('alias_type_idx', $component->name);
$this->assertEquals(new OptionsArray(), $component->options);
$this->assertNull($component->expr);
$this->assertSame([['name' => 'alias_type', 'order' => 'DESC']], $component->columns);
$this->assertSame(
'KEY `alias_type_idx` (`alias_type` DESC)',
Key::build($component)
);
}
public function testParseKeyWithLengthWithOptions(): void
{
$component = Key::parse(
new Parser(),
$this->getTokensList('KEY `alias_type_idx` (`alias_type`(10)) COMMENT \'my comment\',')
);
$this->assertEquals('KEY', $component->type);
$this->assertEquals('alias_type_idx', $component->name);
$this->assertEquals(new OptionsArray(
[
4 => [
'name' => 'COMMENT',
'equals' => false,
'expr' => '\'my comment\'',
'value' => 'my comment',
],
]
), $component->options);
$this->assertNull($component->expr);
$this->assertSame([['name' => 'alias_type', 'length' => 10]], $component->columns);
$this->assertSame(
'KEY `alias_type_idx` (`alias_type`(10)) COMMENT \'my comment\'',
Key::build($component)
);
}
public function testParseKeyWithLengthWithAllOptions(): void
{
$component = Key::parse(
new Parser(),
$this->getTokensList(
// This is not a vary plausible example but it runs
// Only ENGINE_ATTRIBUTE gives a not supported error but is still a valid syntax
'KEY `alias_type_idx` (`alias_type`(10))'
. ' COMMENT \'my comment\' VISIBLE KEY_BLOCK_SIZE=1'
. ' INVISIBLE ENGINE_ATTRIBUTE \'foo\' SECONDARY_ENGINE_ATTRIBUTE=\'bar\' USING BTREE,'
)
);
$this->assertEquals('KEY', $component->type);
$this->assertEquals('alias_type_idx', $component->name);
$this->assertEquals(new OptionsArray(
[
1 => [
'name' => 'KEY_BLOCK_SIZE',
'equals' => true,
'expr' => '1',
'value' => '1',
],
2 => [
'name' => 'USING',
'equals' => false,
'expr' => 'BTREE',
'value' => 'BTREE',
],
4 => [
'name' => 'COMMENT',
'equals' => false,
'expr' => '\'my comment\'',
'value' => 'my comment',
],
5 => [
'name' => 'ENGINE_ATTRIBUTE',
'equals' => true,
'expr' => '\'foo\'',
'value' => 'foo',
],
6 => 'VISIBLE',
12 => 'INVISIBLE',
13 => [
'name' => 'SECONDARY_ENGINE_ATTRIBUTE',
'equals' => true,
'expr' => '\'bar\'',
'value' => 'bar',
],
]
), $component->options);
$this->assertNull($component->expr);
$this->assertSame([['name' => 'alias_type', 'length' => 10]], $component->columns);
}
public function testParseKeyExpressionWithoutOptions(): void
{
$component = Key::parse(
new Parser(),
$this->getTokensList(
'KEY `updated_tz_ind2` ((convert_tz(`cache_updated`,_utf8mb4\'GMT\',_utf8mb4\'GB\'))),'
)
);
$this->assertEquals('KEY', $component->type);
$this->assertEquals('updated_tz_ind2', $component->name);
$this->assertEquals(new OptionsArray(), $component->options);
$expr = new Expression('(convert_tz(`cache_updated`,_utf8mb4\'GMT\',_utf8mb4\'GB\'))');
$expr->function = 'convert_tz';
$this->assertEquals($expr, $component->expr);
$this->assertSame([], $component->columns);
$this->assertSame(
'KEY `updated_tz_ind2` ((convert_tz(`cache_updated`,_utf8mb4\'GMT\',_utf8mb4\'GB\'))) ',
Key::build($component)
);
}
public function testParseKeyExpressionWithOptions(): void
{
$component = Key::parse(
new Parser(),
$this->getTokensList(
'KEY `updated_tz_ind2`'
. ' ((convert_tz(`cache_updated`,_utf8mb4\'GMT\',_utf8mb4\'GB\')))'
. ' COMMENT \'my comment\','
)
);
$this->assertEquals('KEY', $component->type);
$this->assertEquals('updated_tz_ind2', $component->name);
$this->assertEquals(new OptionsArray(
[
4 => [
'name' => 'COMMENT',
'equals' => false,
'expr' => '\'my comment\'',
'value' => 'my comment',
],
]
), $component->options);
$expr = new Expression('(convert_tz(`cache_updated`,_utf8mb4\'GMT\',_utf8mb4\'GB\'))');
$expr->function = 'convert_tz';
$this->assertEquals($expr, $component->expr);
$this->assertSame([], $component->columns);
$this->assertSame(
'KEY `updated_tz_ind2`'
. ' ((convert_tz(`cache_updated`,_utf8mb4\'GMT\',_utf8mb4\'GB\')))'
. ' COMMENT \'my comment\'',
Key::build($component)
);
}
public function testParseKeyExpressionWithOptionsError(): void
{
$parser = new Parser();
$component = Key::parse(
$parser,
$this->getTokensList(
'KEY `updated_tz_ind2` (()convert_tz(`cache_updated`,_utf8mb4\'GMT\',_utf8mb4\'GB\')))'
. ' COMMENT \'my comment\','
)
);
$this->assertEquals('KEY', $component->type);
$this->assertEquals('updated_tz_ind2', $component->name);
$this->assertEquals(new OptionsArray(
[]
), $component->options);
$t = new Token('convert_tz', Token::TYPE_KEYWORD, 33);
$t->position = 25;
$this->assertEquals([
new ParserException(
'Unexpected token.',
$t
),
], $parser->errors);
$expr = new Expression('(convert_tz(`cache_updated`,_utf8mb4\'GMT\',_utf8mb4\'GB\'))');
$expr->function = 'convert_tz';
$this->assertEquals('()(`cache_updated`,_utf8mb4\'GMT\',_utf8mb4\'GB\')', $component->expr);
$this->assertSame([], $component->columns);
$this->assertSame(
'KEY `updated_tz_ind2` (()(`cache_updated`,_utf8mb4\'GMT\',_utf8mb4\'GB\')) ',
Key::build($component)
);
}
public function testParseKeyOneExpressionWithOptions(): void
{
$parser = new Parser();
$component = Key::parse(
$parser,
$this->getTokensList(
'KEY `updated_tz_ind2`'
. ' ('
. '(convert_tz(`cache_updated`,_utf8mb4\'GMT\',_utf8mb4\'GB\')), '
. '(convert_tz(`cache_updated`,_utf8mb4\'GMT\',_utf8mb4\'FR\'))'
. ')'
. ' COMMENT \'my comment\','
)
);
$this->assertEquals('KEY', $component->type);
$this->assertEquals('updated_tz_ind2', $component->name);
$this->assertEquals(new OptionsArray(
[
4 => [
'name' => 'COMMENT',
'equals' => false,
'expr' => '\'my comment\'',
'value' => 'my comment',
],
]
), $component->options);
$this->assertSame([], $parser->errors);
$expr = new Expression(
'(convert_tz(`cache_updated`,_utf8mb4\'GMT\',_utf8mb4\'GB\')),'
. ' (convert_tz(`cache_updated`,_utf8mb4\'GMT\',_utf8mb4\'FR\'))'
);
$expr->function = 'convert_tz';
$this->assertEquals($expr, $component->expr);
$this->assertSame([], $component->columns);
$this->assertSame(
'KEY `updated_tz_ind2` ((convert_tz(`cache_updated`,_utf8mb4\'GMT\',_utf8mb4\'GB\')),'
. ' (convert_tz(`cache_updated`,_utf8mb4\'GMT\',_utf8mb4\'FR\'))'
. ') COMMENT \'my comment\'',
Key::build($component)
);
}
public function testParseKeyMultipleExpressionsWithOptions(): void
{
$parser = new Parser();
$component = Key::parse(
$parser,
$this->getTokensList(
'KEY `updated_tz_ind2`'
. ' ('
. '(convert_tz(`cache_updated`,_utf8mb4\'GMT\',_utf8mb4\'GB\')), '
. '(convert_tz(`cache_updated`,_utf8mb4\'GMT\',_utf8mb4\'FR\')), '
. '(convert_tz(`cache_updated`,_utf8mb4\'GMT\',_utf8mb4\'RU\'))'
. ')'
. ' COMMENT \'my comment\','
)
);
$this->assertEquals('KEY', $component->type);
$this->assertEquals('updated_tz_ind2', $component->name);
$this->assertEquals(new OptionsArray(
[
4 => [
'name' => 'COMMENT',
'equals' => false,
'expr' => '\'my comment\'',
'value' => 'my comment',
],
]
), $component->options);
$expr = new Expression(
'(convert_tz(`cache_updated`,_utf8mb4\'GMT\',_utf8mb4\'GB\')),'
. ' (convert_tz(`cache_updated`,_utf8mb4\'GMT\',_utf8mb4\'FR\')),'
. ' (convert_tz(`cache_updated`,_utf8mb4\'GMT\',_utf8mb4\'RU\'))'
);
$expr->function = 'convert_tz';
$this->assertEquals($expr, $component->expr);
$this->assertSame([], $component->columns);
$this->assertSame([], $parser->errors);
$this->assertSame(
'KEY `updated_tz_ind2` ((convert_tz(`cache_updated`,_utf8mb4\'GMT\',_utf8mb4\'GB\')),'
. ' (convert_tz(`cache_updated`,_utf8mb4\'GMT\',_utf8mb4\'FR\')),'
. ' (convert_tz(`cache_updated`,_utf8mb4\'GMT\',_utf8mb4\'RU\'))'
. ') COMMENT \'my comment\'',
Key::build($component)
);
}
}
@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Components;
use PhpMyAdmin\SqlParser\Components\Limit;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class LimitTest extends TestCase
{
public function testBuildWithoutOffset(): void
{
$component = new Limit(1);
$this->assertEquals(Limit::build($component), '0, 1');
}
public function testBuildWithOffset(): void
{
$component = new Limit(1, 2);
$this->assertEquals(Limit::build($component), '2, 1');
}
/**
* @dataProvider parseProvider
*/
public function testParse(string $test): void
{
$this->runParserTest($test);
}
/**
* @return string[][]
*/
public static function parseProvider(): array
{
return [
['parser/parseLimitErr1'],
['parser/parseLimitErr2'],
];
}
}
@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Components;
use PhpMyAdmin\SqlParser\Components\LockExpression;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class LockExpressionTest extends TestCase
{
public function testParse(): void
{
$component = LockExpression::parse(new Parser(), $this->getTokensList('table1 AS t1 READ LOCAL'));
$this->assertNotNull($component->table);
$this->assertEquals($component->table->table, 'table1');
$this->assertEquals($component->table->alias, 't1');
$this->assertEquals($component->type, 'READ LOCAL');
}
public function testParse2(): void
{
$component = LockExpression::parse(new Parser(), $this->getTokensList('table1 LOW_PRIORITY WRITE'));
$this->assertNotNull($component->table);
$this->assertEquals($component->table->table, 'table1');
$this->assertEquals($component->type, 'LOW_PRIORITY WRITE');
}
/**
* @dataProvider parseErrProvider
*/
public function testParseErr(string $expr, string $error): void
{
$parser = new Parser();
LockExpression::parse($parser, $this->getTokensList($expr));
$errors = $this->getErrorsAsArray($parser);
$this->assertEquals($errors[0][0], $error);
}
/**
* @return string[][]
*/
public static function parseErrProvider(): array
{
return [
[
'table1 AS t1',
'Unexpected end of LOCK expression.',
],
[
'table1 AS t1 READ WRITE',
'Unexpected keyword.',
],
[
'table1 AS t1 READ 2',
'Unexpected token.',
],
];
}
public function testBuild(): void
{
$component = [
LockExpression::parse(new Parser(), $this->getTokensList('table1 AS t1 READ LOCAL')),
LockExpression::parse(new Parser(), $this->getTokensList('table2 LOW_PRIORITY WRITE')),
];
$this->assertEquals(
LockExpression::build($component),
'table1 AS `t1` READ LOCAL, table2 LOW_PRIORITY WRITE'
);
}
}
@@ -0,0 +1,134 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Components;
use PhpMyAdmin\SqlParser\Components\OptionsArray;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class OptionsArrayTest extends TestCase
{
public function testParse(): void
{
$component = OptionsArray::parse(
new Parser(),
$this->getTokensList('A B = /*comment*/ (test) C'),
[
'A' => 1,
'B' => [
2,
'var',
],
'C' => 3,
]
);
$this->assertEquals(
[
1 => 'A',
2 => [
'name' => 'B',
'expr' => '(test)',
'value' => 'test',
'equals' => true,
],
3 => 'C',
],
$component->options
);
}
public function testParseExpr(): void
{
$component = OptionsArray::parse(
new Parser(),
$this->getTokensList('SUM = (3 + 5) RESULT = 8'),
[
'SUM' => [
1,
'expr',
['parenthesesDelimited' => true],
],
'RESULT' => [
2,
'var',
],
]
);
$this->assertEquals('(3 + 5)', (string) $component->has('SUM', true));
$this->assertEquals('8', $component->has('RESULT'));
}
public function testHas(): void
{
$component = OptionsArray::parse(
new Parser(),
$this->getTokensList('A B = /*comment*/ (test) C'),
[
'A' => 1,
'B' => [
2,
'var',
],
'C' => 3,
]
);
$this->assertTrue($component->has('A'));
$this->assertEquals('test', $component->has('B'));
$this->assertTrue($component->has('C'));
$this->assertFalse($component->has('D'));
}
public function testRemove(): void
{
/* Assertion 1 */
$component = new OptionsArray(['a', 'b', 'c']);
$this->assertTrue($component->remove('b'));
$this->assertFalse($component->remove('d'));
$this->assertEquals($component->options, [0 => 'a', 2 => 'c']);
/* Assertion 2 */
$component = OptionsArray::parse(
new Parser(),
$this->getTokensList('A B = /*comment*/ (test) C'),
[
'A' => 1,
'B' => [
2,
'var',
],
'C' => 3,
]
);
$this->assertEquals('test', $component->has('B'));
$component->remove('B');
$this->assertFalse($component->has('B'));
}
public function testMerge(): void
{
$component = new OptionsArray(['a']);
$component->merge(['b', 'c']);
$this->assertEquals($component->options, ['a', 'b', 'c']);
}
public function testBuild(): void
{
$component = new OptionsArray(
[
'ALL',
'SQL_CALC_FOUND_ROWS',
[
'name' => 'MAX_STATEMENT_TIME',
'value' => '42',
'equals' => true,
],
]
);
$this->assertEquals(
OptionsArray::build($component),
'ALL SQL_CALC_FOUND_ROWS MAX_STATEMENT_TIME=42'
);
}
}
@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Components;
use PhpMyAdmin\SqlParser\Components\Expression;
use PhpMyAdmin\SqlParser\Components\OrderKeyword;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class OrderKeywordTest extends TestCase
{
public function testBuild(): void
{
$this->assertEquals(
OrderKeyword::build(
[
new OrderKeyword(new Expression('a'), 'ASC'),
new OrderKeyword(new Expression('b'), 'DESC'),
]
),
'a ASC, b DESC'
);
}
}
@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Components;
use PhpMyAdmin\SqlParser\Components\ParameterDefinition;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class ParameterDefinitionTest extends TestCase
{
public function testParse(): void
{
$component = ParameterDefinition::parse(
new Parser(),
$this->getTokensList('(a INT, b INT')
);
$this->assertEquals('a', $component[0]->name);
$this->assertEquals('b', $component[1]->name);
}
public function testParseComplex(): void
{
$parser = new Parser();
$component = ParameterDefinition::parse(
$parser,
$this->getTokensList('CREATE DEFINER=`root`@`%` PROCEDURE `foo`( $bar int )')
);
$this->assertEquals('$bar', $component[0]->name);
}
}
@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Components;
use PhpMyAdmin\SqlParser\Components\PartitionDefinition;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class PartitionDefinitionTest extends TestCase
{
public function testParse(): void
{
$component = PartitionDefinition::parse(
new Parser(),
$this->getTokensList('PARTITION p0 VALUES LESS THAN(1990)')
);
$this->assertFalse($component->isSubpartition);
$this->assertEquals('p0', $component->name);
$this->assertEquals('LESS THAN', $component->type);
$this->assertEquals('(1990)', $component->expr->expr);
}
public function testParseNameWithUnderscore(): void
{
$component = PartitionDefinition::parse(
new Parser(),
$this->getTokensList('PARTITION 2017_12 VALUES LESS THAN (\'2018-01-01 00:00:00\') ENGINE = MyISAM')
);
$this->assertFalse($component->isSubpartition);
$this->assertEquals('2017_12', $component->name);
$this->assertEquals('LESS THAN', $component->type);
$this->assertEquals('(\'2018-01-01 00:00:00\')', $component->expr->expr);
}
}
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Components;
use PhpMyAdmin\SqlParser\Components\Expression;
use PhpMyAdmin\SqlParser\Components\Reference;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class ReferenceTest extends TestCase
{
public function testParse(): void
{
$component = Reference::parse(new Parser(), $this->getTokensList('tbl (id)'));
$this->assertEquals('tbl', $component->table->table);
$this->assertEquals(['id'], $component->columns);
}
public function testBuild(): void
{
$component = new Reference(new Expression('`tbl`'), ['id']);
$this->assertEquals('`tbl` (`id`)', Reference::build($component));
}
}
@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Components;
use PhpMyAdmin\SqlParser\Components\RenameOperation;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class RenameOperationTest extends TestCase
{
public function testBuild(): void
{
$component = RenameOperation::parse(new Parser(), $this->getTokensList('a TO b, c TO d'));
$this->assertEquals(RenameOperation::build($component), 'a TO b, c TO d');
}
}
@@ -0,0 +1,313 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Lexer;
use PhpMyAdmin\SqlParser\Context;
use PhpMyAdmin\SqlParser\Contexts;
use PhpMyAdmin\SqlParser\Tests\TestCase;
use Throwable;
use function class_exists;
class ContextTest extends TestCase
{
public static function tearDownAfterClass(): void
{
Context::setMode();
}
public function testLoad(): void
{
// Default context is 5.7.0.
$this->assertEquals('\\PhpMyAdmin\\SqlParser\\Contexts\\ContextMySql50700', Context::$loadedContext);
$this->assertArrayHasKey('STORED', Context::$KEYWORDS);
$this->assertArrayNotHasKey('AUTHORS', Context::$KEYWORDS);
// Restoring context.
Context::load('');
$this->assertEquals('\\PhpMyAdmin\\SqlParser\\Contexts\\ContextMySql50700', Context::$defaultContext);
$this->assertArrayHasKey('STORED', Context::$KEYWORDS);
$this->assertArrayNotHasKey('AUTHORS', Context::$KEYWORDS);
}
/**
* Test for loading closest SQL context
*
* @dataProvider contextLoadingProvider
*/
public function testLoadClosest(string $context, ?string $expected): void
{
$this->assertEquals($expected, Context::loadClosest($context));
if ($expected !== null) {
$this->assertEquals('\\PhpMyAdmin\\SqlParser\\Contexts\\Context' . $expected, Context::$loadedContext);
$this->assertTrue(class_exists(Context::$loadedContext));
}
// Restoring context.
Context::load('');
}
/**
* @return array<string, array<int, string|null>>
* @psalm-return array<string, array{string, (string|null)}>
*/
public static function contextLoadingProvider(): array
{
return [
'MySQL match' => [
'MySql50500',
'MySql50500',
],
'MySQL strip' => [
'MySql50712',
'MySql50700',
],
'MySQL fallback' => [
'MySql9897969594',
'MySql50700',
],
'MariaDB match' => [
'MariaDb100000',
'MariaDb100000',
],
'MariaDB stripg' => [
'MariaDb109900',
'MariaDb100000',
],
'MariaDB fallback' => [
'MariaDb990000',
'MariaDb100300',
],
'Invalid' => [
'Sql',
null,
],
];
}
/**
* @dataProvider contextNamesProvider
*/
public function testLoadAll(string $context): void
{
Context::load($context);
$this->assertEquals('\\PhpMyAdmin\\SqlParser\\Contexts\\Context' . $context, Context::$loadedContext);
// Restoring context.
Context::load('');
}
/**
* @return string[][]
*/
public static function contextNamesProvider(): array
{
return [
['MySql50000'],
['MySql50100'],
['MySql50500'],
['MySql50600'],
['MySql50700'],
['MySql80000'],
['MySql80100'],
['MySql80200'],
['MySql80300'],
['MySql80400'],
['MySql90000'],
['MySql90100'],
['MariaDb100000'],
['MariaDb100100'],
['MariaDb100200'],
['MariaDb100300'],
['MariaDb100400'],
['MariaDb100500'],
['MariaDb100600'],
['MariaDb100700'],
['MariaDb100800'],
['MariaDb100900'],
['MariaDb101000'],
['MariaDb101100'],
['MariaDb110000'],
['MariaDb110100'],
['MariaDb110200'],
['MariaDb110300'],
['MariaDb110400'],
['MariaDb110500'],
['MariaDb110600'],
['MariaDb110700'],
];
}
/**
* @dataProvider contextClassesProvider
*/
public function testLoadAllByClass(string $context): void
{
Context::load($context);
$this->assertEquals('\\' . $context, Context::$loadedContext);
// Restoring context.
Context::load('');
}
/**
* @return string[][]
*/
public static function contextClassesProvider(): array
{
return [
[Contexts\ContextMySql50000::class],
[Contexts\ContextMySql50100::class],
[Contexts\ContextMySql50500::class],
[Contexts\ContextMySql50600::class],
[Contexts\ContextMySql50700::class],
[Contexts\ContextMySql80000::class],
[Contexts\ContextMySql80100::class],
[Contexts\ContextMySql80200::class],
[Contexts\ContextMySql80300::class],
[Contexts\ContextMySql80400::class],
[Contexts\ContextMySql90000::class],
[Contexts\ContextMySql90100::class],
[Contexts\ContextMariaDb100000::class],
[Contexts\ContextMariaDb100100::class],
[Contexts\ContextMariaDb100200::class],
[Contexts\ContextMariaDb100300::class],
[Contexts\ContextMariaDb100400::class],
[Contexts\ContextMariaDb100500::class],
[Contexts\ContextMariaDb100600::class],
[Contexts\ContextMariaDb100700::class],
[Contexts\ContextMariaDb100800::class],
[Contexts\ContextMariaDb100900::class],
[Contexts\ContextMariaDb101000::class],
[Contexts\ContextMariaDb101100::class],
[Contexts\ContextMariaDb110000::class],
[Contexts\ContextMariaDb110100::class],
[Contexts\ContextMariaDb110200::class],
[Contexts\ContextMariaDb110300::class],
[Contexts\ContextMariaDb110400::class],
[Contexts\ContextMariaDb110500::class],
[Contexts\ContextMariaDb110600::class],
[Contexts\ContextMariaDb110700::class],
];
}
public function testLoadError(): void
{
$this->expectExceptionMessage(
'Specified context ("\PhpMyAdmin\SqlParser\Contexts\ContextFoo") does not exist.'
);
$this->expectException(Throwable::class);
Context::load('Foo');
}
/**
* @param int|string $mode
*
* @dataProvider providerForTestMode
*/
public function testMode($mode, int $expected): void
{
Context::setMode($mode);
$this->assertSame($expected, Context::getMode());
}
/**
* @return array<int, array<int, int|string>>
* @psalm-return list<array{int|string, int}>
*/
public static function providerForTestMode(): array
{
return [
[0, Context::SQL_MODE_NONE],
[1, 1],
['', Context::SQL_MODE_NONE],
['invalid', Context::SQL_MODE_NONE],
['ALLOW_INVALID_DATES', Context::SQL_MODE_ALLOW_INVALID_DATES],
['ANSI_QUOTES', Context::SQL_MODE_ANSI_QUOTES],
['COMPAT_MYSQL', Context::SQL_MODE_COMPAT_MYSQL],
['ERROR_FOR_DIVISION_BY_ZERO', Context::SQL_MODE_ERROR_FOR_DIVISION_BY_ZERO],
['HIGH_NOT_PRECEDENCE', Context::SQL_MODE_HIGH_NOT_PRECEDENCE],
['IGNORE_SPACE', Context::SQL_MODE_IGNORE_SPACE],
['NO_AUTO_CREATE_USER', Context::SQL_MODE_NO_AUTO_CREATE_USER],
['NO_AUTO_VALUE_ON_ZERO', Context::SQL_MODE_NO_AUTO_VALUE_ON_ZERO],
['NO_BACKSLASH_ESCAPES', Context::SQL_MODE_NO_BACKSLASH_ESCAPES],
['NO_DIR_IN_CREATE', Context::SQL_MODE_NO_DIR_IN_CREATE],
['NO_ENGINE_SUBSTITUTION', Context::SQL_MODE_NO_ENGINE_SUBSTITUTION],
['NO_FIELD_OPTIONS', Context::SQL_MODE_NO_FIELD_OPTIONS],
['NO_KEY_OPTIONS', Context::SQL_MODE_NO_KEY_OPTIONS],
['NO_TABLE_OPTIONS', Context::SQL_MODE_NO_TABLE_OPTIONS],
['NO_UNSIGNED_SUBTRACTION', Context::SQL_MODE_NO_UNSIGNED_SUBTRACTION],
['NO_ZERO_DATE', Context::SQL_MODE_NO_ZERO_DATE],
['NO_ZERO_IN_DATE', Context::SQL_MODE_NO_ZERO_IN_DATE],
['ONLY_FULL_GROUP_BY', Context::SQL_MODE_ONLY_FULL_GROUP_BY],
['PIPES_AS_CONCAT', Context::SQL_MODE_PIPES_AS_CONCAT],
['REAL_AS_FLOAT', Context::SQL_MODE_REAL_AS_FLOAT],
['STRICT_ALL_TABLES', Context::SQL_MODE_STRICT_ALL_TABLES],
['STRICT_TRANS_TABLES', Context::SQL_MODE_STRICT_TRANS_TABLES],
['NO_ENCLOSING_QUOTES', Context::SQL_MODE_NO_ENCLOSING_QUOTES],
['ANSI', Context::SQL_MODE_ANSI],
['DB2', Context::SQL_MODE_DB2],
['MAXDB', Context::SQL_MODE_MAXDB],
['MSSQL', Context::SQL_MODE_MSSQL],
['ORACLE', Context::SQL_MODE_ORACLE],
['POSTGRESQL', Context::SQL_MODE_POSTGRESQL],
['TRADITIONAL', Context::SQL_MODE_TRADITIONAL],
];
}
public function testModeWithCombinedModes(): void
{
Context::setMode(
Context::SQL_MODE_REAL_AS_FLOAT | Context::SQL_MODE_ANSI_QUOTES | Context::SQL_MODE_IGNORE_SPACE
);
$this->assertSame(
Context::SQL_MODE_REAL_AS_FLOAT | Context::SQL_MODE_ANSI_QUOTES | Context::SQL_MODE_IGNORE_SPACE,
Context::getMode()
);
$this->assertTrue(Context::hasMode(Context::SQL_MODE_REAL_AS_FLOAT | Context::SQL_MODE_IGNORE_SPACE));
$this->assertTrue(Context::hasMode(Context::SQL_MODE_ANSI_QUOTES));
$this->assertFalse(Context::hasMode(Context::SQL_MODE_REAL_AS_FLOAT | Context::SQL_MODE_ALLOW_INVALID_DATES));
$this->assertFalse(Context::hasMode(Context::SQL_MODE_ALLOW_INVALID_DATES));
Context::setMode(Context::SQL_MODE_TRADITIONAL);
$this->assertSame(Context::SQL_MODE_TRADITIONAL, Context::getMode());
Context::setMode();
$this->assertSame(Context::SQL_MODE_NONE, Context::getMode());
}
public function testModeWithString(): void
{
Context::setMode('REAL_AS_FLOAT,ANSI_QUOTES,IGNORE_SPACE');
$this->assertSame(
Context::SQL_MODE_REAL_AS_FLOAT | Context::SQL_MODE_ANSI_QUOTES | Context::SQL_MODE_IGNORE_SPACE,
Context::getMode()
);
$this->assertTrue(Context::hasMode(Context::SQL_MODE_REAL_AS_FLOAT | Context::SQL_MODE_IGNORE_SPACE));
$this->assertTrue(Context::hasMode(Context::SQL_MODE_ANSI_QUOTES));
$this->assertFalse(Context::hasMode(Context::SQL_MODE_REAL_AS_FLOAT | Context::SQL_MODE_ALLOW_INVALID_DATES));
$this->assertFalse(Context::hasMode(Context::SQL_MODE_ALLOW_INVALID_DATES));
Context::setMode('TRADITIONAL');
$this->assertSame(Context::SQL_MODE_TRADITIONAL, Context::getMode());
Context::setMode('');
$this->assertSame(Context::SQL_MODE_NONE, Context::getMode());
}
public function testEscape(): void
{
Context::setMode(Context::SQL_MODE_NO_ENCLOSING_QUOTES);
$this->assertEquals('test', Context::escape('test'));
Context::setMode(Context::SQL_MODE_ANSI_QUOTES);
$this->assertEquals('"test"', Context::escape('test'));
Context::setMode();
$this->assertEquals('`test`', Context::escape('test'));
$this->assertEquals(['`a`', '`b`'], Context::escape(['a', 'b']));
}
}
@@ -0,0 +1,138 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Lexer;
use PhpMyAdmin\SqlParser\Context;
use PhpMyAdmin\SqlParser\Tests\TestCase;
use PhpMyAdmin\SqlParser\Token;
class IsMethodsTest extends TestCase
{
public function testIsKeyword(): void
{
$this->assertEquals(1 | Token::FLAG_KEYWORD_RESERVED, Context::isKeyword('SELECT'));
$this->assertEquals(1 | Token::FLAG_KEYWORD_RESERVED, Context::isKeyword('ALL'));
$this->assertEquals(1 | Token::FLAG_KEYWORD_RESERVED, Context::isKeyword('DISTINCT'));
$this->assertEquals(
1 | Token::FLAG_KEYWORD_RESERVED | Token::FLAG_KEYWORD_COMPOSED | Token::FLAG_KEYWORD_KEY,
Context::isKeyword('PRIMARY KEY')
);
$this->assertEquals(
1 | Token::FLAG_KEYWORD_RESERVED | Token::FLAG_KEYWORD_COMPOSED,
Context::isKeyword('CHARACTER SET')
);
$this->assertEquals(1 | Token::FLAG_KEYWORD_RESERVED, Context::isKeyword('FROM', true));
$this->assertNull(Context::isKeyword('MODIFY', true));
$this->assertNull(Context::isKeyword('foo'));
$this->assertNull(Context::isKeyword('bar baz'));
}
public function testIsOperator(): void
{
$this->assertEquals(Token::FLAG_OPERATOR_ARITHMETIC, Context::isOperator('%'));
$this->assertEquals(Token::FLAG_OPERATOR_LOGICAL, Context::isOperator('!'));
$this->assertEquals(Token::FLAG_OPERATOR_LOGICAL, Context::isOperator('&&'));
$this->assertEquals(Token::FLAG_OPERATOR_LOGICAL, Context::isOperator('<=>'));
$this->assertEquals(Token::FLAG_OPERATOR_BITWISE, Context::isOperator('&'));
$this->assertEquals(Token::FLAG_OPERATOR_ASSIGNMENT, Context::isOperator(':='));
$this->assertEquals(Token::FLAG_OPERATOR_SQL, Context::isOperator(','));
$this->assertNull(Context::isOperator('a'));
}
public function testIsWhitespace(): void
{
$this->assertTrue(Context::isWhitespace(' '));
$this->assertTrue(Context::isWhitespace("\r"));
$this->assertTrue(Context::isWhitespace("\n"));
$this->assertTrue(Context::isWhitespace("\t"));
$this->assertFalse(Context::isWhitespace('a'));
$this->assertFalse(Context::isWhitespace("\b"));
$this->assertFalse(Context::isWhitespace("\u1000"));
}
public function testIsComment(): void
{
$this->assertEquals(Token::FLAG_COMMENT_BASH, Context::isComment('#'));
$this->assertEquals(Token::FLAG_COMMENT_C, Context::isComment('/*'));
$this->assertEquals(Token::FLAG_COMMENT_C, Context::isComment('*/'));
$this->assertEquals(Token::FLAG_COMMENT_SQL, Context::isComment('-- '));
$this->assertEquals(Token::FLAG_COMMENT_SQL, Context::isComment("--\t"));
$this->assertEquals(Token::FLAG_COMMENT_SQL, Context::isComment("--\n"));
$this->assertEquals(Token::FLAG_COMMENT_BASH, Context::isComment('# a comment'));
$this->assertEquals(Token::FLAG_COMMENT_C, Context::isComment('/*comment */'));
$this->assertEquals(Token::FLAG_COMMENT_SQL, Context::isComment('-- my comment'));
$this->assertNull(Context::isComment(''));
$this->assertNull(Context::isComment('--not a comment'));
}
public function testIsBool(): void
{
$this->assertTrue(Context::isBool('true'));
$this->assertTrue(Context::isBool('false'));
$this->assertFalse(Context::isBool('tru'));
$this->assertFalse(Context::isBool('falsee'));
}
public function testIsNumber(): void
{
$this->assertTrue(Context::isNumber('+'));
$this->assertTrue(Context::isNumber('-'));
$this->assertTrue(Context::isNumber('.'));
$this->assertTrue(Context::isNumber('0'));
$this->assertTrue(Context::isNumber('1'));
$this->assertTrue(Context::isNumber('2'));
$this->assertTrue(Context::isNumber('3'));
$this->assertTrue(Context::isNumber('4'));
$this->assertTrue(Context::isNumber('5'));
$this->assertTrue(Context::isNumber('6'));
$this->assertTrue(Context::isNumber('7'));
$this->assertTrue(Context::isNumber('8'));
$this->assertTrue(Context::isNumber('9'));
$this->assertTrue(Context::isNumber('e'));
$this->assertTrue(Context::isNumber('E'));
}
public function testIsString(): void
{
$this->assertEquals(Token::FLAG_STRING_SINGLE_QUOTES, Context::isString("'"));
$this->assertEquals(Token::FLAG_STRING_DOUBLE_QUOTES, Context::isString('"'));
$this->assertEquals(Token::FLAG_STRING_SINGLE_QUOTES, Context::isString("'foo bar'"));
$this->assertEquals(Token::FLAG_STRING_DOUBLE_QUOTES, Context::isString('"foo bar"'));
$this->assertNull(Context::isString(''));
$this->assertNull(Context::isString('foo bar'));
}
public function testIsSymbol(): void
{
$this->assertEquals(Token::FLAG_SYMBOL_VARIABLE, Context::isSymbol('@'));
$this->assertEquals(Token::FLAG_SYMBOL_BACKTICK, Context::isSymbol('`'));
$this->assertEquals(Token::FLAG_SYMBOL_VARIABLE, Context::isSymbol('@id'));
$this->assertEquals(Token::FLAG_SYMBOL_BACKTICK, Context::isSymbol('`id`'));
$this->assertNull(Context::isSymbol(''));
$this->assertNull(Context::isSymbol('id'));
}
public function testisSeparator(): void
{
$this->assertTrue(Context::isSeparator('+'));
$this->assertTrue(Context::isSeparator('.'));
$this->assertFalse(Context::isSeparator('1'));
$this->assertFalse(Context::isSeparator('E'));
$this->assertFalse(Context::isSeparator('_'));
}
}
@@ -0,0 +1,101 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Lexer;
use PhpMyAdmin\SqlParser\Exceptions\LexerException;
use PhpMyAdmin\SqlParser\Lexer;
use PhpMyAdmin\SqlParser\Tests\TestCase;
use function sprintf;
class LexerTest extends TestCase
{
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testError(): void
{
$lexer = new Lexer('');
$lexer->error('error #1', 'foo', 1, 2);
$lexer->error(
sprintf('%2$s #%1$d', 2, 'error'),
'bar',
3,
4
);
$this->assertEquals(
$lexer->errors,
[
new LexerException('error #1', 'foo', 1, 2),
new LexerException('error #2', 'bar', 3, 4),
]
);
}
public function testErrorStrict(): void
{
$this->expectExceptionCode(4);
$this->expectExceptionMessage('strict error');
$this->expectException(LexerException::class);
$lexer = new Lexer('');
$lexer->strict = true;
$lexer->error('strict error', 'foo', 1, 4);
}
/**
* @dataProvider lexProvider
*/
public function testLex(string $test): void
{
$this->runParserTest($test);
}
/**
* @return string[][]
*/
public static function lexProvider(): array
{
return [
['lexer/lex'],
['lexer/lexUtf8'],
['lexer/lexBool'],
['lexer/lexComment'],
['lexer/lexCommentEnd'],
['lexer/lexDelimiter'],
['lexer/lexDelimiter2'],
['lexer/lexDelimiterErr1'],
['lexer/lexDelimiterErr2'],
['lexer/lexDelimiterErr3'],
['lexer/lexDelimiterLen'],
['lexer/lexKeyword'],
['lexer/lexKeyword2'],
['lexer/lexNumber'],
['lexer/lexOperator'],
['lexer/lexOperatorStarIsArithmetic'],
['lexer/lexOperatorStarIsWildcard'],
['lexer/lexEmptyCStyleComment'],
['lexer/lexString'],
['lexer/lexStringErr1'],
['lexer/lexSymbol'],
['lexer/lexSymbolErr1'],
['lexer/lexSymbolErr2'],
['lexer/lexSymbolErr3'],
['lexer/lexSymbolUser1'],
['lexer/lexSymbolUser2'],
['lexer/lexSymbolUser3'],
['lexer/lexSymbolUser4_mariadb_100400'],
['lexer/lexSymbolUser5_mariadb_100400'],
['lexer/lexWhitespace'],
['lexer/lexLabel1'],
['lexer/lexLabel2'],
['lexer/lexNoLabel'],
['lexer/lexWildcardThenComment'],
];
}
}
@@ -0,0 +1,92 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Lexer;
use PhpMyAdmin\SqlParser\Tests\TestCase;
use PhpMyAdmin\SqlParser\Token;
class TokenTest extends TestCase
{
public function testExtractKeyword(): void
{
$tok = new Token('SelecT', Token::TYPE_KEYWORD, Token::FLAG_KEYWORD_RESERVED);
$this->assertEquals($tok->value, 'SELECT');
$tok = new Token('aS', Token::TYPE_KEYWORD, Token::FLAG_KEYWORD_RESERVED);
$this->assertEquals($tok->value, 'AS');
}
public function testExtractWhitespace(): void
{
$tok = new Token(" \t \r \n ", Token::TYPE_WHITESPACE);
$this->assertEquals($tok->value, ' ');
}
public function testExtractBool(): void
{
$tok = new Token('false', Token::TYPE_BOOL);
$this->assertFalse($tok->value);
$tok = new Token('True', Token::TYPE_BOOL);
$this->assertTrue($tok->value);
}
public function testExtractNumber(): void
{
$tok = new Token('--42', Token::TYPE_NUMBER, Token::FLAG_NUMBER_NEGATIVE);
$this->assertEquals($tok->value, 42);
$tok = new Token('---42', Token::TYPE_NUMBER, Token::FLAG_NUMBER_NEGATIVE);
$this->assertEquals($tok->value, -42);
$tok = new Token('0xFE', Token::TYPE_NUMBER, Token::FLAG_NUMBER_HEX);
$this->assertEquals($tok->value, 0xFE);
$tok = new Token('-0xEF', Token::TYPE_NUMBER, Token::FLAG_NUMBER_NEGATIVE | Token::FLAG_NUMBER_HEX);
$this->assertEquals($tok->value, -0xEF);
$tok = new Token('3.14', Token::TYPE_NUMBER, Token::FLAG_NUMBER_FLOAT);
$this->assertEquals($tok->value, 3.14);
}
public function testExtractString(): void
{
$tok = new Token('"foo bar "', Token::TYPE_STRING);
$this->assertEquals($tok->value, 'foo bar ');
$tok = new Token("' bar foo '", Token::TYPE_STRING);
$this->assertEquals($tok->value, ' bar foo ');
$tok = new Token("'\''", Token::TYPE_STRING);
$this->assertEquals($tok->value, '\'');
$tok = new Token('"\c\d\e\f\g\h\i\j\k\l\m\p\q\s\u\v\w\x\y\z"', Token::TYPE_STRING);
$this->assertEquals($tok->value, 'cdefghijklmpqsuvwxyz');
}
public function testExtractSymbol(): void
{
$tok = new Token('@foo', Token::TYPE_SYMBOL, Token::FLAG_SYMBOL_VARIABLE);
$this->assertEquals($tok->value, 'foo');
$tok = new Token('`foo`', Token::TYPE_SYMBOL, Token::FLAG_SYMBOL_BACKTICK);
$this->assertEquals($tok->value, 'foo');
$tok = new Token('@`foo`', Token::TYPE_SYMBOL, Token::FLAG_SYMBOL_VARIABLE);
$this->assertEquals($tok->value, 'foo');
$tok = new Token(':foo', Token::TYPE_SYMBOL, Token::FLAG_SYMBOL_PARAMETER);
$this->assertEquals($tok->value, 'foo');
$tok = new Token('?', Token::TYPE_SYMBOL, Token::FLAG_SYMBOL_PARAMETER);
$this->assertEquals($tok->value, '?');
}
public function testInlineToken(): void
{
$token = new Token(" \r \n \t ");
$this->assertEquals($token->getInlineToken(), ' \r \n \t ');
}
}
@@ -0,0 +1,177 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Lexer;
use PhpMyAdmin\SqlParser\Tests\TestCase;
use PhpMyAdmin\SqlParser\Token;
use PhpMyAdmin\SqlParser\TokensList;
use function count;
class TokensListTest extends TestCase
{
/**
* ArrayObj of tokens that are used for testing.
*
* @var Token[]
*/
public $tokens;
/**
* Test setup.
*/
public function setUp(): void
{
parent::setUp();
$this->tokens = [
new Token('SELECT', Token::TYPE_KEYWORD),
new Token(' ', Token::TYPE_WHITESPACE),
new Token('*', Token::TYPE_OPERATOR),
new Token(' ', Token::TYPE_WHITESPACE),
new Token('FROM', Token::TYPE_KEYWORD, Token::FLAG_KEYWORD_RESERVED),
new Token(' ', Token::TYPE_WHITESPACE),
new Token('`test`', Token::TYPE_SYMBOL),
new Token(' ', Token::TYPE_WHITESPACE),
new Token('WHERE', Token::TYPE_KEYWORD, Token::FLAG_KEYWORD_RESERVED),
new Token(' ', Token::TYPE_WHITESPACE),
new Token('name', Token::TYPE_NONE),
new Token('=', Token::TYPE_OPERATOR),
new Token('fa', Token::TYPE_NONE),
];
}
public function testBuild(): void
{
$list = new TokensList($this->tokens);
$this->assertEquals('SELECT * FROM `test` WHERE name=fa', TokensList::build($list));
}
public function testAdd(): void
{
$list = new TokensList();
foreach ($this->tokens as $token) {
$list->add($token);
}
$this->assertEquals(new TokensList($this->tokens), $list);
}
public function testGetNext(): void
{
$list = new TokensList($this->tokens);
$this->assertEquals($this->tokens[0], $list->getNext());
$this->assertEquals($this->tokens[2], $list->getNext());
$this->assertEquals($this->tokens[4], $list->getNext());
$this->assertEquals($this->tokens[6], $list->getNext());
$this->assertEquals($this->tokens[8], $list->getNext());
$this->assertEquals($this->tokens[10], $list->getNext());
$this->assertEquals($this->tokens[11], $list->getNext());
$this->assertEquals($this->tokens[12], $list->getNext());
$this->assertNull($list->getNext());
}
public function testGetPrevious(): void
{
$list = new TokensList($this->tokens);
$list->idx = 7;
$this->assertEquals($this->tokens[6], $list->getPrevious());
$this->assertEquals($this->tokens[4], $list->getPrevious());
$this->assertEquals($this->tokens[2], $list->getPrevious());
$this->assertEquals($this->tokens[0], $list->getPrevious());
$this->assertNull($list->getPrevious());
}
public function testGetNextOfType(): void
{
$list = new TokensList($this->tokens);
$this->assertEquals($this->tokens[0], $list->getNextOfType(Token::TYPE_KEYWORD));
$this->assertEquals($this->tokens[4], $list->getNextOfType([Token::TYPE_KEYWORD]));
$this->assertEquals($this->tokens[6], $list->getNextOfType([Token::TYPE_KEYWORD, Token::TYPE_SYMBOL]));
$this->assertEquals($this->tokens[8], $list->getNextOfType([Token::TYPE_KEYWORD, Token::TYPE_SYMBOL]));
$this->assertNull($list->getNextOfType(Token::TYPE_KEYWORD));
}
public function testGetPreviousOfType(): void
{
$list = new TokensList($this->tokens);
$list->idx = 9;
$this->assertEquals($this->tokens[8], $list->getPreviousOfType([Token::TYPE_KEYWORD, Token::TYPE_SYMBOL]));
$this->assertEquals($this->tokens[6], $list->getPreviousOfType([Token::TYPE_KEYWORD, Token::TYPE_SYMBOL]));
$this->assertEquals($this->tokens[4], $list->getPreviousOfType([Token::TYPE_KEYWORD]));
$this->assertEquals($this->tokens[0], $list->getPreviousOfType(Token::TYPE_KEYWORD));
$this->assertNull($list->getPreviousOfType(Token::TYPE_KEYWORD));
}
public function testGetNextOfTypeAndFlag(): void
{
$list = new TokensList($this->tokens);
$this->assertEquals($this->tokens[4], $list->getNextOfTypeAndFlag(
Token::TYPE_KEYWORD,
Token::FLAG_KEYWORD_RESERVED
));
$this->assertEquals($this->tokens[8], $list->getNextOfTypeAndFlag(
Token::TYPE_KEYWORD,
Token::FLAG_KEYWORD_RESERVED
));
$this->assertNull($list->getNextOfTypeAndFlag(Token::TYPE_KEYWORD, Token::FLAG_KEYWORD_RESERVED));
}
public function testGetNextOfTypeAndValue(): void
{
$list = new TokensList($this->tokens);
$this->assertEquals($this->tokens[0], $list->getNextOfTypeAndValue(Token::TYPE_KEYWORD, 'SELECT'));
$this->assertNull($list->getNextOfTypeAndValue(Token::TYPE_KEYWORD, 'SELECT'));
}
public function testArrayAccess(): void
{
$list = new TokensList();
// offsetSet(NULL, $value)
foreach ($this->tokens as $token) {
$list[] = $token;
}
// offsetSet($offset, $value)
$list[2] = $this->tokens[2];
// offsetSet($offset, $value) with overflowed offset
$list[$list->count] = $this->tokens[1];
$this->assertSame($list[$list->count - 1], $this->tokens[1]);
$this->assertSame($list->count, count($this->tokens) + 1);
// offsetGet($offset) with negative offset
$this->assertNull($list[-1]);
// offsetGet($offset) with overflow offset
$this->assertNull($list[$list->count]);
// offsetGet($offset)
for ($i = 0, $count = count($this->tokens); $i < $count; ++$i) {
$this->assertSame($this->tokens[$i], $list[$i]);
}
// offsetExists($offset)
$this->assertArrayHasKey(2, $list);
$this->assertArrayNotHasKey(14, $list);
$this->assertArrayNotHasKey(-8, $list);
// offsetUnset($offset)
$currentCountTokens = $list->count;
unset($list[2]);
$newCountTokens = $list->count;
$this->assertSame($this->tokens[3], $list[2]);
$this->assertSame($currentCountTokens - 1, $newCountTokens);
// offsetUnset($offset) with invalid offset (negative or overflowed)
$currentListTokens = $list->tokens;
unset($list[-1]);
$this->assertSame($currentListTokens, $list->tokens);
$this->assertSame($newCountTokens, $list->count); // No unset actually performed.
unset($list[999]);
$this->assertSame($currentListTokens, $list->tokens);
$this->assertSame($newCountTokens, $list->count); // No unset actually performed.
}
}
@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Misc;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class BugsTest extends TestCase
{
/**
* @dataProvider bugProvider
*/
public function testBug(string $test): void
{
$this->runParserTest($test);
}
/**
* @return string[][]
*/
public static function bugProvider(): array
{
return [
['bugs/fuzz1'],
['bugs/fuzz2'],
['bugs/fuzz3'],
['bugs/fuzz4'],
['bugs/gh9'],
['bugs/gh14'],
['bugs/gh16'],
['bugs/gh202'],
['bugs/gh234'],
['bugs/gh317'],
['bugs/gh412'],
['bugs/gh478'],
['bugs/gh492'],
['bugs/gh496'],
['bugs/gh498'],
['bugs/gh499'],
['bugs/gh508'],
['bugs/gh511'],
['bugs/pma11800'],
['bugs/pma11836'],
['bugs/pma11843'],
['bugs/pma11879'],
];
}
}
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Misc;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class ParameterTest extends TestCase
{
/**
* @dataProvider parameterProvider
*/
public function testParameter(string $test): void
{
$this->runParserTest($test);
}
/**
* @return string[][]
*/
public static function parameterProvider(): array
{
return [
['misc/parseParameter'],
['misc/parseParameter2'],
];
}
}
@@ -0,0 +1,126 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Misc;
use PhpMyAdmin\SqlParser\Tests\TestCase;
use PhpMyAdmin\SqlParser\UtfString;
use Throwable;
use function chr;
class UtfStringTest extends TestCase
{
/**
* Sample phrase in French.
*/
public const TEST_PHRASE = 'Les naïfs ægithales hâtifs pondant à Noël où il '
. 'gèle sont sûrs d\'être déçus en voyant leurs drôles d\'œufs abîmés.';
/**
* The length of the sample phrase.
*/
public const TEST_PHRASE_LEN = 113;
public function testArrayAccess(): void
{
$str = new UtfString(self::TEST_PHRASE);
// offsetExists
$this->assertArrayHasKey(self::TEST_PHRASE_LEN - 1, $str);
$this->assertArrayNotHasKey(-1, $str);
$this->assertArrayNotHasKey(self::TEST_PHRASE_LEN, $str);
// offsetGet
$this->assertEquals('.', $str[self::TEST_PHRASE_LEN - 1]);
$this->assertNull($str[-1]);
$this->assertNull($str[self::TEST_PHRASE_LEN]);
}
public function testSet(): void
{
$this->expectExceptionMessage('Not implemented.');
$this->expectException(Throwable::class);
$str = new UtfString('');
$str[0] = 'a';
}
public function testUnset(): void
{
$this->expectExceptionMessage('Not implemented.');
$this->expectException(Throwable::class);
$str = new UtfString('');
unset($str[0]);
}
public function testGetCharLength(): void
{
$this->assertEquals(1, UtfString::getCharLength(chr(0x00))); // 00000000
$this->assertEquals(1, UtfString::getCharLength(chr(0x7F))); // 01111111
$this->assertEquals(2, UtfString::getCharLength(chr(0xC0))); // 11000000
$this->assertEquals(2, UtfString::getCharLength(chr(0xDF))); // 11011111
$this->assertEquals(3, UtfString::getCharLength(chr(0xE0))); // 11100000
$this->assertEquals(3, UtfString::getCharLength(chr(0xEF))); // 11101111
$this->assertEquals(4, UtfString::getCharLength(chr(0xF0))); // 11110000
$this->assertEquals(4, UtfString::getCharLength(chr(0xF7))); // 11110111
$this->assertEquals(5, UtfString::getCharLength(chr(0xF8))); // 11111000
$this->assertEquals(5, UtfString::getCharLength(chr(0xFB))); // 11111011
$this->assertEquals(6, UtfString::getCharLength(chr(0xFC))); // 11111100
$this->assertEquals(6, UtfString::getCharLength(chr(0xFD))); // 11111101
}
public function testToString(): void
{
$str = new UtfString(self::TEST_PHRASE);
$this->assertEquals(self::TEST_PHRASE, (string) $str);
}
/**
* Test access to string.
*
* @dataProvider utf8StringsProvider
*/
public function testAccess(string $text, ?string $pos10, ?string $pos20): void
{
$str = new UtfString($text);
$this->assertEquals($pos10, $str->offsetGet(10));
$this->assertEquals($pos20, $str->offsetGet(20));
$this->assertEquals($pos10, $str->offsetGet(10));
}
/**
* @return array<string, array<int, string|null>>
* @psalm-return array<string, array{string, (string|null), (string|null)}>
*/
public static function utf8StringsProvider(): array
{
return [
'ascii' => [
'abcdefghijklmnopqrstuvwxyz',
'k',
'u',
],
'unicode' => [
'áéíóúýěřťǔǐǒǎšďȟǰǩľžčǚň',
'ǐ',
'č',
],
'emoji' => [
'😂😄😃😀😊😉😍😘😚😗😂👿😮😨😱😠😡😤😖😆😋👯',
'😂',
'😋',
],
'iso' => [
"P\xf8\xed\xb9ern\xec \xbelu\xbbou\xe8k\xfd k\xf3d \xfap\xecl \xef\xe1belsk\xe9 k\xf3dy",
null,
null,
],
];
}
}
@@ -0,0 +1,114 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class AlterStatementTest extends TestCase
{
/**
* @dataProvider alterProvider
*/
public function testAlter(string $test): void
{
$this->runParserTest($test);
}
/**
* @return string[][]
*/
public static function alterProvider(): array
{
return [
['parser/parseAlter'],
['parser/parseAlter2'],
['parser/parseAlter3'],
['parser/parseAlter4'],
['parser/parseAlter5'],
['parser/parseAlter6'],
['parser/parseAlter7'],
['parser/parseAlter8'],
['parser/parseAlter9'],
['parser/parseAlter10'],
['parser/parseAlter11'],
['parser/parseAlter12'],
['parser/parseAlter13'],
['parser/parseAlter14'],
['parser/parseAlterErr'],
['parser/parseAlterErr2'],
['parser/parseAlterErr3'],
['parser/parseAlterErr4'],
['parser/parseAlterTableRenameIndex1'],
['parser/parseAlterTableRenameIndex2'],
['parser/parseAlterTableRenameKey1'],
['parser/parseAlterTableRenameKey2'],
['parser/parseAlterTablePartitionByRange1'],
['parser/parseAlterTablePartitionByRange2'],
['parser/parseAlterTableCoalescePartition'],
['parser/parseAlterTableAddColumnWithCheck'],
['parser/parseAlterTableAddSpatialIndex1'],
['parser/parseAlterTableDropAddIndex1'],
['parser/parseAlterTableDropColumn1'],
['parser/parseAlterTableModifyColumn'],
['parser/parseAlterTableModifyColumnEnum1'],
['parser/parseAlterTableModifyColumnEnum2'],
['parser/parseAlterTableModifyColumnEnum3'],
['parser/parseAlterWithInvisible'],
['parser/parseAlterTableCharacterSet1'],
['parser/parseAlterTableCharacterSet2'],
['parser/parseAlterTableCharacterSet3'],
['parser/parseAlterTableCharacterSet4'],
['parser/parseAlterTableCharacterSet5'],
['parser/parseAlterTableCharacterSet6'],
['parser/parseAlterTableCharacterSet7'],
['parser/parseAlterUser'],
['parser/parseAlterUser1'],
['parser/parseAlterUser2'],
['parser/parseAlterUser3'],
['parser/parseAlterUser4'],
['parser/parseAlterUser5'],
['parser/parseAlterUser6'],
['parser/parseAlterUser7'],
['parser/parseAlterUser8'],
['parser/parseAlterUser9'],
['parser/parseAlterUser10'],
['parser/parseAlterEvent'],
['parser/parseAlterEvent2'],
['parser/parseAlterEvent3'],
['parser/parseAlterEvent4'],
['parser/parseAlterEvent5'],
['parser/parseAlterEvent6'],
['parser/parseAlterEvent7'],
['parser/parseAlterEvent8'],
['parser/parseAlterEvent9'],
['parser/parseAlterEventComplete'],
['parser/parseAlterEventErr'],
['parser/parseAlterEventOnScheduleAt'],
['parser/parseAlterEventOnScheduleAt2'],
['parser/parseAlterEventOnScheduleEvery'],
['parser/parseAlterEventOnScheduleEvery2'],
['parser/parseAlterEventOnScheduleEvery3'],
['parser/parseAlterEventOnScheduleEvery4'],
['parser/parseAlterEventOnScheduleEvery5'],
['parser/parseAlterEventOnScheduleEvery6'],
['parser/parseAlterEventWithDefiner'],
['parser/parseAlterEventWithOtherDefiners'],
['parser/parseAlterRenameColumn'],
['parser/parseAlterRenameColumns'],
['parser/parseAlterFunction1'],
['parser/parseAlterFunction2'],
['parser/parseAlterFunction3'],
['parser/parseAlterFunction4'],
['parser/parseAlterFunction5'],
['parser/parseAlterFunction6'],
['parser/parseAlterProcedure1'],
['parser/parseAlterProcedure2'],
['parser/parseAlterProcedure3'],
['parser/parseAlterProcedure4'],
['parser/parseAlterProcedure5'],
['parser/parseAlterProcedure6'],
];
}
}
@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class AnalyzeStatementTest extends TestCase
{
/**
* @dataProvider analyzeProvider
*/
public function testAnalyze(string $test): void
{
$this->runParserTest($test);
}
/**
* @return string[][]
*/
public static function analyzeProvider(): array
{
return [
['parser/parseAnalyzeTable'],
['parser/parseAnalyzeTable1'],
['parser/parseAnalyzeErr1'],
['parser/parseAnalyzeErr2'],
];
}
}
@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class CallStatementTest extends TestCase
{
/**
* @dataProvider callProvider
*/
public function testCall(string $test): void
{
$this->runParserTest($test);
}
/**
* @return string[][]
*/
public static function callProvider(): array
{
return [
['parser/parseCall'],
['parser/parseCall2'],
['parser/parseCall3'],
['parser/parseCall4'],
['parser/parseCall5'],
];
}
}
@@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class CreateStatementTest extends TestCase
{
/**
* @dataProvider createProvider
*/
public function testCreate(string $test): void
{
$this->runParserTest($test);
}
/**
* @return string[][]
*/
public static function createProvider(): array
{
return [
['parser/parseCreateDatabase'],
['parser/parseCreateDatabaseErr'],
['parser/parseCreateFunction'],
['parser/parseCreateFunctionErr1'],
['parser/parseCreateFunctionErr2'],
['parser/parseCreateFunctionErr3'],
['parser/parseCreateProcedure'],
['parser/parseCreateProcedure1'],
['parser/parseCreateProcedure2'],
['parser/parseCreateProcedure3'],
['parser/parseCreateProcedure4'],
['parser/parseCreateSchema'],
['parser/parseCreateSchemaErr'],
['parser/parseCreateTable'],
['parser/parseCreateTable2'],
['parser/parseCreateTable3'],
['parser/parseCreateTable4'],
['parser/parseCreateTable5'],
['parser/parseCreateTable6'],
['parser/parseCreateTable7'],
['parser/parseCreateTable8'],
['parser/parseCreateTable9'],
['parser/parseCreateTable10'],
['parser/parseCreateTable11'],
['parser/parseCreateTable12'],
['parser/parseCreateTable13'],
['parser/parseCreateTable14'],
['parser/parseCreateTable15'],
['parser/parseCreateTable16'],
['parser/parseCreateTable17'],
['parser/parseCreateTable18'],
['parser/parseCreateTableErr1'],
['parser/parseCreateTableErr2'],
['parser/parseCreateTableErr3'],
['parser/parseCreateTableErr4'],
['parser/parseCreateTableErr5'],
['parser/parseCreateTableSelect'],
['parser/parseCreateTableAsSelect'],
['parser/parseCreateTableLike'],
['parser/parseCreateTableSpatial'],
['parser/parseCreateTableSRID'],
['parser/parseCreateTableTimestampWithPrecision'],
['parser/parseCreateTableEnforcedCheck'],
['parser/parseCreateTableNotEnforcedCheck'],
['parser/parseCreateTableWithInvisibleKey'],
['parser/parseCreateTableWithParser'],
['parser/parseCreateTrigger'],
['parser/parseCreateUser1'],
['parser/parseCreateUser2'],
['parser/parseCreateView'],
['parser/parseCreateView2'],
['parser/parseCreateView3'],
['parser/parseCreateView4'],
['parser/parseCreateView5'],
['parser/parseCreateViewMultiple'],
['parser/parseCreateViewWithoutQuotes'],
['parser/parseCreateViewWithQuotes'],
['parser/parseCreateViewWithWrongSyntax'],
['parser/parseCreateViewWithUnion'],
['parser/parseCreateViewAsWithAs'],
['parser/parseCreateOrReplaceView1'],
];
}
}
@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class DeleteStatementTest extends TestCase
{
/**
* @dataProvider deleteProvider
*/
public function testDelete(string $test): void
{
$this->runParserTest($test);
}
/**
* @return string[][]
*/
public static function deleteProvider(): array
{
return [
['parser/parseDelete'],
['parser/parseDelete2'],
['parser/parseDelete3'],
['parser/parseDelete4'],
['parser/parseDelete5'],
['parser/parseDelete6'],
['parser/parseDelete7'],
['parser/parseDelete8'],
['parser/parseDelete9'],
['parser/parseDelete10'],
['parser/parseDelete11'],
['parser/parseDelete12'],
['parser/parseDeleteErr1'],
['parser/parseDeleteErr2'],
['parser/parseDeleteErr3'],
['parser/parseDeleteErr4'],
['parser/parseDeleteErr5'],
['parser/parseDeleteErr6'],
['parser/parseDeleteErr7'],
['parser/parseDeleteErr8'],
['parser/parseDeleteErr9'],
['parser/parseDeleteErr10'],
['parser/parseDeleteErr11'],
['parser/parseDeleteErr12'],
['parser/parseDeleteJoin'],
];
}
}
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class DropStatementTest extends TestCase
{
/**
* @dataProvider dropProvider
*/
public function testDrop(string $test): void
{
$this->runParserTest($test);
}
/**
* @return string[][]
*/
public static function dropProvider(): array
{
return [
['parser/parseDrop'],
['parser/parseDrop2'],
];
}
}
@@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class ExplainStatementTest extends TestCase
{
/**
* @dataProvider explainProvider
*/
public function testExplain(string $test): void
{
$this->runParserTest($test);
}
/**
* @return string[][]
*/
public static function explainProvider(): array
{
return [
['parser/parseExplain'],
['parser/parseExplain1'],
['parser/parseExplain2'],
['parser/parseExplain3'],
['parser/parseExplain4'],
['parser/parseExplain5'],
['parser/parseExplain6'],
['parser/parseExplain7'],
['parser/parseExplain8'],
['parser/parseExplain9'],
['parser/parseExplain10'],
['parser/parseExplain11'],
['parser/parseExplain12'],
['parser/parseExplain13'],
['parser/parseExplain14'],
['parser/parseExplainErr'],
['parser/parseExplainErr1'],
['parser/parseExplainErr2'],
['parser/parseExplainErr3'],
];
}
}
@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class InsertStatementTest extends TestCase
{
/**
* @dataProvider insertProvider
*/
public function testInsert(string $test): void
{
$this->runParserTest($test);
}
/**
* @return string[][]
*/
public static function insertProvider(): array
{
return [
['parser/parseInsert'],
['parser/parseInsertFunction'],
['parser/parseInsertSelect'],
['parser/parseInsertOnDuplicateKey'],
['parser/parseInsertSetOnDuplicateKey'],
['parser/parseInsertSelectOnDuplicateKey'],
['parser/parseInsertOnDuplicateKeyErr'],
['parser/parseInsertErr'],
['parser/parseInsertErr2'],
['parser/parseInsertIntoErr'],
];
}
}
@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Parser;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Statements\KillStatement;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class KillStatementTest extends TestCase
{
/** @dataProvider killProvider */
public function testKill(string $test): void
{
$this->runParserTest($test);
}
/**
* @return string[][]
*/
public static function killProvider(): array
{
return [
['parser/parseKill'],
['parser/parseKill2'],
['parser/parseKill3'],
['parser/parseKillConnection'],
['parser/parseKillQuery'],
['parser/parseKillErr1'],
['parser/parseKillErr2'],
['parser/parseKillErr3'],
['parser/parseKillErr4'],
];
}
/** @dataProvider buildKillProvider */
public function testBuildKill(string $sql): void
{
$parser = new Parser($sql);
$this->assertCount(1, $parser->statements);
$statement = $parser->statements[0];
$this->assertInstanceOf(KillStatement::class, $statement);
$builtSql = $statement->build();
$this->assertEquals($sql, $builtSql);
}
/**
* @return array<int, array<int, string>>
* @psalm-return list<list<string>>
*/
public static function buildKillProvider(): array
{
return [
['KILL (SELECT 3 + 4)'],
['KILL QUERY 4'],
['KILL CONNECTION 5'],
['KILL 6'],
['KILL QUERY (SELECT 7)'],
['KILL SOFT QUERY (SELECT 8)'],
['KILL HARD 9'],
['KILL USER 10'],
['KILL SOFT (SELECT 1)'],
['KILL (2)'],
['KILL QUERY ID (2)'],
['KILL QUERY ID (SELECT ID FROM INFORMATION_SCHEMA.PROCESSLIST LIMIT 0, 1)'],
];
}
}
@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Parser;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class LoadStatementTest extends TestCase
{
public function testLoadOptions(): void
{
$data = $this->getData('parser/parseLoad1');
$parser = new Parser($data['query']);
$stmt = $parser->statements[0];
$this->assertEquals(10, $stmt->options->has('CONCURRENT'));
}
/**
* @dataProvider loadProvider
*/
public function testLoad(string $test): void
{
$this->runParserTest($test);
}
/**
* @return string[][]
*/
public static function loadProvider(): array
{
return [
['parser/parseLoad1'],
['parser/parseLoad2'],
['parser/parseLoad3'],
['parser/parseLoad4'],
['parser/parseLoad5'],
['parser/parseLoad6'],
['parser/parseLoad7'],
['parser/parseLoad8'],
['parser/parseLoadErr1'],
['parser/parseLoadErr2'],
['parser/parseLoadErr3'],
['parser/parseLoadErr4'],
['parser/parseLoadErr5'],
['parser/parseLoadErr6'],
];
}
}
@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class LockStatementTest extends TestCase
{
/**
* @dataProvider lockProvider
*/
public function testLock(string $test): void
{
$this->runParserTest($test);
}
/**
* @return string[][]
*/
public static function lockProvider(): array
{
return [
['parser/parseLock1'],
['parser/parseLock2'],
['parser/parseLock3'],
['parser/parseLock4'],
['parser/parseLock5'],
['parser/parseLockErr1'],
['parser/parseLockErr2'],
['parser/parseLockErr3'],
['parser/parseLockErr4'],
['parser/parseLockErr5'],
['parser/parseLockErr6'],
['parser/parseLockErr7'],
['parser/parseLockErr8'],
['parser/parseLockErr9'],
['parser/parseLockErr10'],
['parser/parseUnlock1'],
['parser/parseUnlockErr1'],
];
}
}
@@ -0,0 +1,120 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Parser;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Statements\SetStatement;
use PhpMyAdmin\SqlParser\Statements\TransactionStatement;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class ParserLongExportsTest extends TestCase
{
public function testMysqldump(): void
{
$sql = <<<SQL
-- MySQL dump 10.13 Distrib 5.7.24, for Linux (x86_64)ldump e-nocleg-2-dev 01-te
--
-- Host: localhost Database: x
-- ------------------------------------------------------
-- Server version 5.7.24
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
SQL;
$expectedSql = [
'SET @OLD_CHARACTER_SET_CLIENT = @@CHARACTER_SET_CLIENT',
'SET @OLD_CHARACTER_SET_RESULTS = @@CHARACTER_SET_RESULTS',
'SET @OLD_COLLATION_CONNECTION = @@COLLATION_CONNECTION',
'SET NAMES utf8',
'SET @OLD_TIME_ZONE = @@TIME_ZONE',
"SET TIME_ZONE = '+00:00'",
'SET @OLD_UNIQUE_CHECKS = @@UNIQUE_CHECKS, UNIQUE_CHECKS = 0',
'SET @OLD_FOREIGN_KEY_CHECKS = @@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS = 0',
"SET @OLD_SQL_MODE = @@SQL_MODE, SQL_MODE = 'NO_AUTO_VALUE_ON_ZERO'",
'SET @OLD_SQL_NOTES = @@SQL_NOTES, SQL_NOTES = 0',
];
$parser = new Parser($sql, true);
$parser->parse();
$sql = [];
foreach ($parser->statements as $stmt) {
$sql[] = $stmt->__toString();
}
$this->assertEquals($expectedSql, $sql);
}
public function testParsephpMyAdminDump(): void
{
$data = $this->getData('parser/parsephpMyAdminExport1');
$parser = new Parser($data['query']);
$collectedSetStatements = [];
foreach ($parser->statements as $statement) {
if ($statement instanceof TransactionStatement) {
foreach ($statement->statements as $transactionStatement) {
if (! $transactionStatement instanceof SetStatement) {
continue;
}
$collectedSetStatements[] = $transactionStatement->build();
}
continue;
}
if (! $statement instanceof SetStatement) {
continue;
}
$collectedSetStatements[] = $statement->build();
}
$this->assertEquals([
'SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"',
'SET AUTOCOMMIT = 0',
'SET time_zone = "+00:00"',
'SET @OLD_CHARACTER_SET_CLIENT = @@CHARACTER_SET_CLIENT',
'SET @OLD_CHARACTER_SET_RESULTS = @@CHARACTER_SET_RESULTS',
'SET @OLD_COLLATION_CONNECTION = @@COLLATION_CONNECTION',
'SET NAMES utf8mb4',
'SET CHARACTER_SET_CLIENT = @OLD_CHARACTER_SET_CLIENT',
'SET CHARACTER_SET_RESULTS = @OLD_CHARACTER_SET_RESULTS',
'SET COLLATION_CONNECTION = @OLD_COLLATION_CONNECTION',
], $collectedSetStatements);
foreach ($parser->statements as $stmt) {
// Check they all build
$this->assertIsString($stmt->build());
}
}
/**
* @dataProvider exportFileProvider
*/
public function testParseExport(string $test): void
{
$this->runParserTest($test);
}
/**
* @return string[][]
*/
public static function exportFileProvider(): array
{
return [
['parser/parsephpMyAdminExport1'],
];
}
}
@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Parser;
use PhpMyAdmin\SqlParser\Exceptions\ParserException;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
use PhpMyAdmin\SqlParser\Token;
use PhpMyAdmin\SqlParser\TokensList;
use function sprintf;
class ParserTest extends TestCase
{
/**
* @dataProvider parseProvider
*/
public function testParse(string $test): void
{
$this->runParserTest($test);
}
/**
* @return string[][]
*/
public static function parseProvider(): array
{
return [
['parser/parse'],
['parser/parse2'],
['parser/parseDelimiter'],
];
}
public function testUnrecognizedStatement(): void
{
$parser = new Parser('SELECT 1; FROM');
$this->assertEquals(
'Unrecognized statement type.',
$parser->errors[0]->getMessage()
);
}
public function testUnrecognizedKeyword(): void
{
$parser = new Parser('SELECT 1 FROM foo PARTITION(bar, baz) AS');
$this->assertEquals(
'Unrecognized keyword.',
$parser->errors[0]->getMessage()
);
}
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testError(): void
{
$parser = new Parser(new TokensList());
$parser->error('error #1', new Token('foo'), 1);
$parser->error(sprintf('%2$s #%1$d', 2, 'error'), new Token('bar'), 2);
$this->assertEquals(
$parser->errors,
[
new ParserException('error #1', new Token('foo'), 1),
new ParserException('error #2', new Token('bar'), 2),
]
);
}
public function testErrorStrict(): void
{
$this->expectExceptionCode(3);
$this->expectExceptionMessage('strict error');
$this->expectException(ParserException::class);
$parser = new Parser(new TokensList());
$parser->strict = true;
$parser->error('strict error', new Token('foo'), 3);
}
}
@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class PurgeStatementTest extends TestCase
{
/**
* @dataProvider purgeProvider
*/
public function testPurge(string $test): void
{
$this->runParserTest($test);
}
/**
* @return string[][]
*/
public static function purgeProvider(): array
{
return [
['parser/parsePurge'],
['parser/parsePurge2'],
['parser/parsePurge3'],
['parser/parsePurge4'],
['parser/parsePurgeErr'],
['parser/parsePurgeErr2'],
['parser/parsePurgeErr3'],
];
}
}
@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class RenameStatementTest extends TestCase
{
/**
* @dataProvider renameProvider
*/
public function testRename(string $test): void
{
$this->runParserTest($test);
}
/**
* @return string[][]
*/
public static function renameProvider(): array
{
return [
['parser/parseRename'],
['parser/parseRename2'],
['parser/parseRenameErr1'],
['parser/parseRenameErr2'],
['parser/parseRenameErr3'],
['parser/parseRenameErr4'],
['parser/parseRenameErr5'],
];
}
}
@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class ReplaceStatementTest extends TestCase
{
/**
* @dataProvider replaceProvider
*/
public function testReplace(string $test): void
{
$this->runParserTest($test);
}
/**
* @return string[][]
*/
public static function replaceProvider(): array
{
return [
['parser/parseReplace'],
['parser/parseReplace2'],
['parser/parseReplaceValues'],
['parser/parseReplaceSet'],
['parser/parseReplaceSelect'],
['parser/parseReplaceErr'],
['parser/parseReplaceErr2'],
['parser/parseReplaceErr3'],
['parser/parseReplaceIntoErr'],
];
}
}
@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class RestoreStatementTest extends TestCase
{
/**
* @dataProvider restoreProvider
*/
public function testRestore(string $test): void
{
$this->runParserTest($test);
}
/**
* @return string[][]
*/
public static function restoreProvider(): array
{
return [
['parser/parseRestore'],
];
}
}
@@ -0,0 +1,110 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Parser;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class SelectStatementTest extends TestCase
{
public function testSelectOptions(): void
{
$data = $this->getData('parser/parseSelect');
$parser = new Parser($data['query']);
$stmt = $parser->statements[0];
$this->assertEquals(10, $stmt->options->has('MAX_STATEMENT_TIME'));
}
/**
* @dataProvider selectProvider
*/
public function testSelect(string $test): void
{
$this->runParserTest($test);
}
/**
* @return string[][]
*/
public static function selectProvider(): array
{
return [
['parser/parseSelect2'],
['parser/parseSelect3'],
['parser/parseSelect4'],
['parser/parseSelect5'],
['parser/parseSelect6'],
['parser/parseSelect7'],
['parser/parseSelect8'],
['parser/parseSelect9'],
['parser/parseSelect10'],
['parser/parseSelect11'],
['parser/parseSelect12'],
['parser/parseSelect13'],
['parser/parseSelect14'],
['parser/parseSelect15'],
['parser/parseSelect16'],
['parser/parseSelectAggregateWithPartitionAndAlias'],
['parser/parseSelectErr1'],
['parser/parseSelectErr2'],
['parser/parseSelectNested'],
['parser/parseSelectCase1'],
['parser/parseSelectCase2'],
['parser/parseSelectCase3'],
['parser/parseSelectCase4'],
['parser/parseSelectCase5'],
['parser/parseSelectCaseErr1'],
['parser/parseSelectCaseErr2'],
['parser/parseSelectCaseErr3'],
['parser/parseSelectCaseErr4'],
['parser/parseSelectCaseErr5'],
['parser/parseSelectCaseAlias1'],
['parser/parseSelectCaseAlias2'],
['parser/parseSelectCaseAlias3'],
['parser/parseSelectCaseAlias4'],
['parser/parseSelectCaseAlias5'],
['parser/parseSelectCaseAlias6'],
['parser/parseSelectCaseAliasErr1'],
['parser/parseSelectCaseAliasErr2'],
['parser/parseSelectCaseAliasErr3'],
['parser/parseSelectCaseAliasErr4'],
['parser/parseSelectExists'],
['parser/parseSelectIntoOptions1'],
['parser/parseSelectIntoOptions2'],
['parser/parseSelectIntoOptions3'],
['parser/parseSelectJoinCross'],
['parser/parseSelectJoinNatural'],
['parser/parseSelectJoinNaturalLeft'],
['parser/parseSelectJoinNaturalRight'],
['parser/parseSelectJoinNaturalLeftOuter'],
['parser/parseSelectJoinNaturalRightOuter'],
['parser/parseSelectJoinMultiple'],
['parser/parseSelectJoinMultiple2'],
['parser/parseSelectWrongOrder'],
['parser/parseSelectWrongOrder2'],
['parser/parseSelectEndOptions1'],
['parser/parseSelectEndOptions2'],
['parser/parseSelectEndOptionsErr'],
['parser/parseSelectUnion'],
['parser/parseSelectUnion2'],
['parser/parseSelectWhere'],
['parser/parseSelectWhereCollate'],
['parser/parseSelectIndexHint1'],
['parser/parseSelectIndexHint2'],
['parser/parseSelectOrderByIsNull'],
['parser/parseSelectIndexHintErr1'],
['parser/parseSelectIndexHintErr2'],
['parser/parseSelectIndexHintErr3'],
['parser/parseSelectIndexHintErr4'],
['parser/parseSelectWithParenthesis'],
['parser/parseSelectOrderByComment'],
['parser/parseSelectOverAlias_mariadb_100600'],
['parser/parseSelectGroupBy'],
['parser/parseSelectGroupByErr'],
['parser/parseSelectGroupByWithComments'],
['parser/parseTable1'],
];
}
}
@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class SetStatementTest extends TestCase
{
/**
* @dataProvider setProvider
*/
public function testSet(string $test): void
{
$this->runParserTest($test);
}
/**
* @return string[][]
*/
public static function setProvider(): array
{
return [
['parser/parseSetCharset'],
['parser/parseSetCharsetError'],
['parser/parseSetCharacterSet'],
['parser/parseSetCharacterSetError'],
['parser/parseAlterTableSetAutoIncrementError'],
['parser/parseSetNames'],
['parser/parseSetNamesError'],
['parser/parseSetError1'],
['parser/parseInsertIntoSet'],
['parser/parseSetVariable'],
['parser/parseSetVariable2'],
['parser/parseSetGlobalVariable'],
];
}
}
@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Parser;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class TransactionStatementTest extends TestCase
{
public function testBuildWithoutEnd(): void
{
$data = $this->getData('parser/parseTransaction4');
$parser = new Parser($data['query']);
$stmt = $parser->statements[0];
$this->assertEquals(
'START TRANSACTION;SET time_zone = "+00:00";',
$stmt->build()
);
}
/**
* @dataProvider transactionProvider
*/
public function testTransaction(string $test): void
{
$this->runParserTest($test);
}
/**
* @return string[][]
*/
public static function transactionProvider(): array
{
return [
['parser/parseTransaction'],
['parser/parseTransaction2'],
['parser/parseTransaction3'],
['parser/parseTransaction4'],
['parser/parseTransaction5'],
['parser/parseTransaction6'],
['parser/parseTransaction7'],
['parser/parseTransactionErr1'],
];
}
}
@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
class UpdateStatementTest extends TestCase
{
/**
* @dataProvider updateProvider
*/
public function testUpdate(string $test): void
{
$this->runParserTest($test);
}
/**
* @return string[][]
*/
public static function updateProvider(): array
{
return [
['parser/parseUpdate1'],
['parser/parseUpdate2'],
['parser/parseUpdate3'],
['parser/parseUpdate4'],
['parser/parseUpdate5'],
['parser/parseUpdate6'],
['parser/parseUpdate7'],
['parser/parseUpdateErr'],
['parser/parseUpdateEmptySet'],
];
}
}
@@ -0,0 +1,148 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Parser;
use PhpMyAdmin\SqlParser\Components\WithKeyword;
use PhpMyAdmin\SqlParser\Lexer;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
use stdClass;
class WithStatementTest extends TestCase
{
/**
* @dataProvider parseWith
*/
public function testParse(string $test): void
{
$this->runParserTest($test);
}
/**
* @return string[][]
*/
public static function parseWith(): array
{
return [
['parser/parseWithStatement'],
['parser/parseWithStatement1'],
['parser/parseWithStatement2'],
['parser/parseWithStatement3'],
['parser/parseWithStatement4'],
['parser/parseWithStatement5'],
['parser/parseWithStatement6'],
['parser/parseWithStatement7'],
['parser/parseWithStatementErr'],
['parser/parseWithStatementErr1'],
['parser/parseWithStatementErr2'],
['parser/parseWithStatementErr3'],
['parser/parseWithStatementErr4'],
['parser/parseWithStatementErr5'],
['parser/parseWithStatementErr6'],
['parser/parseWithStatementErr7'],
['parser/parseWithStatementErr8'],
];
}
public function testWith(): void
{
$sql = <<<SQL
WITH categories(identifier, name, parent_id) AS (
SELECT c.identifier, c.name, c.parent_id FROM category c WHERE c.identifier = 'a'
UNION ALL
SELECT c.identifier, c.name, c.parent_id FROM categories, category c WHERE c.identifier = categories.parent_id
), foo AS ( SELECT * FROM test )
SELECT * FROM categories
SQL;
$lexer = new Lexer($sql);
$lexerErrors = $this->getErrorsAsArray($lexer);
$this->assertCount(0, $lexerErrors);
$parser = new Parser($lexer->list);
$parserErrors = $this->getErrorsAsArray($parser);
$this->assertCount(0, $parserErrors);
$this->assertCount(1, $parser->statements);
// phpcs:disable Generic.Files.LineLength.TooLong
$expected = <<<SQL
WITH categories(identifier, name, parent_id) AS (SELECT c.identifier, c.name, c.parent_id FROM category AS `c` WHERE c.identifier = 'a' UNION ALL SELECT c.identifier, c.name, c.parent_id FROM categories, category AS `c` WHERE c.identifier = categories.parent_id), foo AS (SELECT * FROM test) SELECT * FROM categories
SQL;
// phpcs:enable
$this->assertEquals($expected, $parser->statements[0]->build());
}
public function testWithHasErrors(): void
{
$sql = <<<SQL
WITH categories(identifier, name, parent_id) AS (
SOMETHING * FROM foo
)
SELECT * FROM categories
SQL;
$lexer = new Lexer($sql);
$lexerErrors = $this->getErrorsAsArray($lexer);
$this->assertCount(0, $lexerErrors);
$parser = new Parser($lexer->list);
$parserErrors = $this->getErrorsAsArray($parser);
$this->assertCount(4, $parserErrors);
}
public function testWithEmbedParenthesis(): void
{
$sql = <<<SQL
WITH categories AS (
SELECT * FROM (SELECT * FROM foo)
)
SELECT * FROM categories
SQL;
$lexer = new Lexer($sql);
$lexerErrors = $this->getErrorsAsArray($lexer);
$this->assertCount(0, $lexerErrors);
$parser = new Parser($lexer->list);
$parserErrors = $this->getErrorsAsArray($parser);
$this->assertCount(0, $parserErrors);
// phpcs:disable Generic.Files.LineLength.TooLong
$expected = <<<SQL
WITH categories AS (SELECT * FROM (SELECT * FROM foo)) SELECT * FROM categories
SQL;
// phpcs:enable
$this->assertEquals($expected, $parser->statements[0]->build());
}
public function testWithHasUnclosedParenthesis(): void
{
$sql = <<<SQL
WITH categories(identifier, name, parent_id) AS (
SELECT * FROM (SELECT * FROM foo
)
SELECT * FROM categories
SQL;
$lexer = new Lexer($sql);
$lexerErrors = $this->getErrorsAsArray($lexer);
$this->assertCount(0, $lexerErrors);
$parser = new Parser($lexer->list);
$parserErrors = $this->getErrorsAsArray($parser);
$this->assertEquals($parserErrors[0][0], 'A closing bracket was expected.');
}
public function testBuildWrongWithKeyword(): void
{
$this->expectExceptionMessage('Can not build a component that is not a WithKeyword');
WithKeyword::build(new stdClass());
}
public function testBuildBadWithKeyword(): void
{
$this->expectExceptionMessage('No statement inside WITH');
WithKeyword::build(new WithKeyword('test'));
}
}
@@ -0,0 +1,173 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests;
use PhpMyAdmin\SqlParser\Context;
use PhpMyAdmin\SqlParser\Exceptions\LexerException;
use PhpMyAdmin\SqlParser\Exceptions\ParserException;
use PhpMyAdmin\SqlParser\Lexer;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Token;
use PhpMyAdmin\SqlParser\TokensList;
use PhpMyAdmin\SqlParser\Tools\CustomJsonSerializer;
use PHPUnit\Framework\TestCase as BaseTestCase;
use function file_get_contents;
use function str_contains;
use function strpos;
use function substr;
/**
* Implements useful methods for testing.
*/
abstract class TestCase extends BaseTestCase
{
public function setUp(): void
{
global $lang;
// This line makes sure the test suite uses English so we can assert
// on the error messages, if it is not here you will need to use
// LC_ALL=C ./vendor/bin/phpunit
// Users can have French language as default on their OS
// That would make the assertions fail
$lang = 'en';
Context::load();
}
/**
* Gets the token list generated by lexing this query.
*
* @param string $query the query to be lexed
*/
public function getTokensList(string $query): TokensList
{
$lexer = new Lexer($query);
return $lexer->list;
}
/**
* Gets the errors as an array.
*
* @param Lexer|Parser $obj object containing the errors
*
* @return array<int, array<int, Token|string|int>>
* @psalm-return (
* $obj is Lexer
* ? list<array{string, string, int, int}>
* : list<array{string, Token|null, int}>
* )
*/
public function getErrorsAsArray($obj): array
{
$ret = [];
if ($obj instanceof Lexer) {
/** @var LexerException $err */
foreach ($obj->errors as $err) {
$ret[] = [$err->getMessage(), $err->ch, $err->pos, (int) $err->getCode()];
}
} elseif ($obj instanceof Parser) {
/** @var ParserException $err */
foreach ($obj->errors as $err) {
$ret[] = [$err->getMessage(), $err->token, (int) $err->getCode()];
}
}
return $ret;
}
/**
* Gets test's input and expected output.
*
* @param string $name the name of the test
*
* @return array<string, string|Lexer|Parser|array<string, array<int, int|string|Token>[]>|null>
* @psalm-return array{
* query: string,
* lexer: Lexer,
* parser: Parser|null,
* errors: array{lexer: list<array{string, string, int, int}>, parser: list<array{string, Token, int}>}
* }
*/
public function getData(string $name): array
{
$serializedData = file_get_contents('tests/data/' . $name . '.out');
$this->assertIsString($serializedData);
$serializer = new CustomJsonSerializer();
$data = $serializer->unserialize($serializedData);
$this->assertIsArray($data);
$this->assertArrayHasKey('query', $data);
$this->assertArrayHasKey('lexer', $data);
$this->assertArrayHasKey('parser', $data);
$this->assertArrayHasKey('errors', $data);
$this->assertIsString($data['query']);
$this->assertInstanceOf(Lexer::class, $data['lexer']);
if ($data['parser'] !== null) {
$this->assertInstanceOf(Parser::class, $data['parser']);
}
$this->assertIsArray($data['errors']);
$this->assertArrayHasKey('lexer', $data['errors']);
$this->assertArrayHasKey('parser', $data['errors']);
$this->assertIsArray($data['errors']['lexer']);
$this->assertIsArray($data['errors']['parser']);
$data['query'] = file_get_contents('tests/data/' . $name . '.in');
$this->assertIsString($data['query']);
return $data;
}
/**
* Runs a test.
*
* @param string $name the name of the test
*/
public function runParserTest(string $name): void
{
/**
* Test's data.
*/
$data = $this->getData($name);
if (str_contains($name, '/ansi/')) {
// set mode if appropriate
Context::setMode(Context::SQL_MODE_ANSI_QUOTES);
}
$mariaDbPos = strpos($name, '_mariadb_');
if ($mariaDbPos !== false) {// Keep in sync with TestGenerator.php
// set context
$mariaDbVersion = (int) substr($name, $mariaDbPos + 9, 6);
Context::load('MariaDb' . $mariaDbVersion);
}
// Lexer.
$lexer = new Lexer($data['query']);
$lexerErrors = $this->getErrorsAsArray($lexer);
$lexer->errors = [];
// Parser.
$parser = empty($data['parser']) ? null : new Parser($lexer->list);
$parserErrors = [];
if ($parser !== null) {
$parserErrors = $this->getErrorsAsArray($parser);
$parser->errors = [];
}
// Testing objects.
$this->assertEquals($data['lexer'], $lexer);
$this->assertEquals($data['parser'], $parser);
// Testing errors.
$this->assertEquals($data['errors']['parser'], $parserErrors);
$this->assertEquals($data['errors']['lexer'], $lexerErrors);
// reset mode after test run
Context::setMode();
}
}
@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Tools;
use PhpMyAdmin\SqlParser\Tests\TestCase;
use PhpMyAdmin\SqlParser\Token;
use PhpMyAdmin\SqlParser\Tools\ContextGenerator;
use function file_get_contents;
use function getcwd;
class ContextGeneratorTest extends TestCase
{
public function testFormatName(): void
{
$name = ContextGenerator::formatName('Invalid00Format00');
$this->assertEquals('Invalid00Format00', $name);
$name = ContextGenerator::formatName('MySql80000');
$this->assertEquals('MySQL 8.0', $name);
$name = ContextGenerator::formatName('MariaDb100200');
$this->assertEquals('MariaDB 10.2', $name);
$name = ContextGenerator::formatName('MariaDb100000');
$this->assertEquals('MariaDB 10.0', $name);
$name = ContextGenerator::formatName('FutureDBMS45784012500');
$this->assertEquals('FutureDBMS 4.57.84.1.25', $name);
}
public function testSortWords(): void
{
$wordsArray = ['41' => ['GEOMETRYCOLLECTION', 'DATE'], '35' => ['SCHEMA', 'REPEAT', 'VALUES']];
ContextGenerator::sortWords($wordsArray);
$this->assertEquals([
'41' => ['DATE', 'GEOMETRYCOLLECTION'],
'35' => ['REPEAT', 'SCHEMA', 'VALUES'],
], $wordsArray);
}
public function testReadWords(): void
{
$testFiles = [getcwd() . '/tests/Tools/contexts/testContext.txt'];
$readWords = ContextGenerator::readWords($testFiles);
$this->assertEquals([
Token::FLAG_KEYWORD | Token::FLAG_KEYWORD_RESERVED => [
'RESERVED',
'RESERVED2',
'RESERVED3',
'RESERVED4',
'RESERVED5',
],
Token::FLAG_KEYWORD | Token::FLAG_KEYWORD_FUNCTION => ['FUNCTION'],
Token::FLAG_KEYWORD | Token::FLAG_KEYWORD_DATA_TYPE => ['DATATYPE'],
Token::FLAG_KEYWORD | Token::FLAG_KEYWORD_KEY => ['KEYWORD'],
Token::FLAG_KEYWORD => ['NO_FLAG'],
Token::FLAG_KEYWORD | Token::FLAG_KEYWORD_RESERVED | Token::FLAG_KEYWORD_COMPOSED => ['COMPOSED KEYWORD'],
], $readWords);
}
public function testGenerate(): void
{
$testFiles = [getcwd() . '/tests/Tools/contexts/testContext.txt'];
$readWords = ContextGenerator::readWords($testFiles);
ContextGenerator::printWords($readWords);
$options = [
'keywords' => $readWords,
'name' => 'MYSQL TEST',
'class' => 'TestContext',
'link' => 'https://www.phpmyadmin.net/contribute',
];
$generatedTemplate = ContextGenerator::generate($options);
$expectedTemplate = file_get_contents(getcwd() . '/tests/Tools/templates/TestContext.php');
$this->assertEquals($expectedTemplate, $generatedTemplate);
}
}
@@ -0,0 +1,16 @@
RESERVED (R)
RESERVED2 (R)
RESERVED3 (R)
RESERVED4 (R)
reserved5 (R)
FUNCTION (F)
DATATYPE (D)
KEYWORD (K)
NO_FLAG
COMPOSED KEYWORD
FUNCTION
@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Contexts;
use PhpMyAdmin\SqlParser\Context;
use PhpMyAdmin\SqlParser\Token;
/**
* Context for MYSQL TEST.
*
* This class was auto-generated from tools/contexts/*.txt.
* Use tools/run_generators.sh for update.
*
* @see https://www.phpmyadmin.net/contribute
*/
class TestContext extends Context
{
/**
* List of keywords.
*
* The value associated to each keyword represents its flags.
*
* @see Token
*
* @var array<string,int>
* @phpstan-var non-empty-array<non-empty-string,Token::FLAG_KEYWORD_*|int>
*/
public static $KEYWORDS = [
'NO_FLAG' => Token::FLAG_KEYWORD,
'RESERVED' => Token::FLAG_KEYWORD | Token::FLAG_KEYWORD_RESERVED,
'RESERVED2' => Token::FLAG_KEYWORD | Token::FLAG_KEYWORD_RESERVED,
'RESERVED3' => Token::FLAG_KEYWORD | Token::FLAG_KEYWORD_RESERVED,
'RESERVED4' => Token::FLAG_KEYWORD | Token::FLAG_KEYWORD_RESERVED,
'RESERVED5' => Token::FLAG_KEYWORD | Token::FLAG_KEYWORD_RESERVED,
'COMPOSED KEYWORD' => Token::FLAG_KEYWORD | Token::FLAG_KEYWORD_RESERVED | Token::FLAG_KEYWORD_COMPOSED,
'DATATYPE' => Token::FLAG_KEYWORD | Token::FLAG_KEYWORD_DATA_TYPE,
'KEYWORD' => Token::FLAG_KEYWORD | Token::FLAG_KEYWORD_KEY,
'FUNCTION' => Token::FLAG_KEYWORD | Token::FLAG_KEYWORD_FUNCTION,
];
}
@@ -0,0 +1,358 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Utils;
use PhpMyAdmin\SqlParser\Tests\TestCase;
use PhpMyAdmin\SqlParser\Utils\BufferedQuery;
use function count;
use function str_split;
class BufferedQueryTest extends TestCase
{
/**
* @param array<string, bool> $options
* @param string[] $expected
* @psalm-param array{delimiter?: non-empty-string, parse_delimiter?: bool, add_delimiter?: bool} $options
* @psalm-param positive-int $chunkSize
*
* @dataProvider extractProvider
*/
public function testExtract(
string $query,
int $chunkSize,
array $options,
array $expected
): void {
$chunks = str_split($query, $chunkSize);
$count = count($chunks);
/**
* The array of extracted statements.
*/
$statements = [];
/**
* The `BufferedQuery` instance used for extraction.
*/
$bq = new BufferedQuery('', $options);
// Feeding chunks and extracting queries.
$i = 0;
while ($i < $count) {
$stmt = $bq->extract();
if ($stmt) {
$statements[] = $stmt;
} else {
$bq->query .= $chunks[$i++];
}
}
// Feeding ended, extracting remaining queries.
while ($stmt = $bq->extract(true)) {
$statements[] = $stmt;
}
$this->assertEquals($expected, $statements);
}
/**
* @return array<int, array<int, int|string|string[]|bool[]>>
* @psalm-return list<array{string, positive-int, array{parse_delimiter: bool, add_delimiter: bool}, string[]}>
*/
public static function extractProvider(): array
{
$query =
'/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;' . "\n" .
'/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;' . "\n" .
'/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;' . "\n" .
'/*!40101 SET NAMES utf8mb4 */;' . "\n" .
'' . "\n" .
'SET SQL_MODE = \'NO_AUTO_VALUE_ON_ZERO\';' . "\n" .
'SET time_zone = "+00:00";' . "\n" .
'' . "\n" .
'/* a comment */ DELIMITER $$' . "\n" .
'' . "\n" .
'# Bash-like comment sytanx.' . "\n" .
'CREATE DEFINER=`root`@`localhost` PROCEDURE `film_in_stock` (IN `p_film_id` ' .
'INT, IN `p_store_id` INT, OUT `p_film_count` INT) READS SQL DATA' . "\n" .
'BEGIN' . "\n" .
' SELECT inventory_id' . "\n" .
' FROM inventory' . "\n" .
' WHERE film_id = p_film_id' . "\n" .
' AND store_id = p_store_id' . "\n" .
' AND inventory_in_stock(inventory_id);' . "\n" .
'' . "\n" .
' SELECT FOUND_ROWS() INTO p_film_count;' . "\n" .
'END$$' . "\n" .
'' . "\n" .
'DELIMITER ;' . "\n" .
'' . "\n" .
'-- --------------------------------------------------------' . "\n" .
'' . "\n" .
'--' . "\n" .
'-- Table structure for `actor`' . "\n" .
'--' . "\n" .
'' . "\n" .
'/* C-like comment syntax. */' . "\n" .
'CREATE TABLE IF NOT EXISTS `actor` (' . "\n" .
'`actor_id` SMALLINT(5) UNSIGNED NOT NULL,' . "\n" .
'`first_name` VARCHAR(45) NOT NULL,' . "\n" .
'`last_name` VARCHAR(45) NOT NULL,' . "\n" .
'`last_update` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP' . "\n" .
') ENGINE=InnoDB DEFAULT CHARSET=utf8;' . "\n" .
'' . "\n" .
'/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;' . "\n" .
'/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;' . "\n" .
'/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */';
return [
[
"SELECT '\'';\nSELECT '\'';",
8,
[
'parse_delimiter' => true,
'add_delimiter' => true,
],
[
"SELECT '\'';",
"SELECT '\'';",
],
],
[
'SELECT \\',
8,
[
'parse_delimiter' => false,
'add_delimiter' => false,
],
['SELECT \\'],
],
[
"CREATE TABLE `test` (\n" .
" `txt` varchar(10)\n" .
");\n" .
"INSERT INTO `test` (`txt`) VALUES('abc');\n" .
"INSERT INTO `test` (`txt`) VALUES('\\\\');\n" .
"INSERT INTO `test` (`txt`) VALUES('xyz');\n",
8,
[
'parse_delimiter' => true,
'add_delimiter' => true,
],
[
"CREATE TABLE `test` (\n" .
" `txt` varchar(10)\n" .
');',
"INSERT INTO `test` (`txt`) VALUES('abc');",
"INSERT INTO `test` (`txt`) VALUES('\\\\');",
"INSERT INTO `test` (`txt`) VALUES('xyz');",
],
],
[
'SELECT """""""";' .
'SELECT """\\\\"""',
8,
[
'parse_delimiter' => true,
'add_delimiter' => true,
],
[
'SELECT """""""";',
'SELECT """\\\\"""',
],
],
[
'DELIMITER A_VERY_LONG_DEL' . "\n" .
'SELECT 1 A_VERY_LONG_DEL' . "\n" .
'DELIMITER ;',
3,
[
'parse_delimiter' => true,
'add_delimiter' => true,
],
[
'DELIMITER A_VERY_LONG_DEL',
'SELECT 1 A_VERY_LONG_DEL',
'DELIMITER ;',
],
],
[
$query,
32,
[
'parse_delimiter' => false,
'add_delimiter' => false,
],
[
'/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */',
'/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */',
'/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */',
'/*!40101 SET NAMES utf8mb4 */',
'SET SQL_MODE = \'NO_AUTO_VALUE_ON_ZERO\'',
'SET time_zone = "+00:00"',
'# Bash-like comment sytanx.' . "\n" .
'CREATE DEFINER=`root`@`localhost` PROCEDURE `film_in_stock` (IN `p_film_id` ' .
'INT, IN `p_store_id` INT, OUT `p_film_count` INT) READS SQL DATA' . "\n" .
'BEGIN' . "\n" .
' SELECT inventory_id' . "\n" .
' FROM inventory' . "\n" .
' WHERE film_id = p_film_id' . "\n" .
' AND store_id = p_store_id' . "\n" .
' AND inventory_in_stock(inventory_id);' . "\n" .
'' . "\n" .
' SELECT FOUND_ROWS() INTO p_film_count;' . "\n" .
'END',
'-- --------------------------------------------------------' . "\n" .
'' . "\n" .
'--' . "\n" .
'-- Table structure for `actor`' . "\n" .
'--' . "\n" .
'' . "\n" .
'/* C-like comment syntax. */' . "\n" .
'CREATE TABLE IF NOT EXISTS `actor` (' . "\n" .
'`actor_id` SMALLINT(5) UNSIGNED NOT NULL,' . "\n" .
'`first_name` VARCHAR(45) NOT NULL,' . "\n" .
'`last_name` VARCHAR(45) NOT NULL,' . "\n" .
'`last_update` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP' . "\n" .
') ENGINE=InnoDB DEFAULT CHARSET=utf8',
'/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */',
'/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */',
'/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */',
],
],
[
$query,
32,
[
'parse_delimiter' => true,
'add_delimiter' => false,
],
[
'/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */',
'/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */',
'/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */',
'/*!40101 SET NAMES utf8mb4 */',
'SET SQL_MODE = \'NO_AUTO_VALUE_ON_ZERO\'',
'SET time_zone = "+00:00"',
'/* a comment */ DELIMITER $$',
'# Bash-like comment sytanx.' . "\n" .
'CREATE DEFINER=`root`@`localhost` PROCEDURE `film_in_stock` (IN `p_film_id` ' .
'INT, IN `p_store_id` INT, OUT `p_film_count` INT) READS SQL DATA' . "\n" .
'BEGIN' . "\n" .
' SELECT inventory_id' . "\n" .
' FROM inventory' . "\n" .
' WHERE film_id = p_film_id' . "\n" .
' AND store_id = p_store_id' . "\n" .
' AND inventory_in_stock(inventory_id);' . "\n" .
'' . "\n" .
' SELECT FOUND_ROWS() INTO p_film_count;' . "\n" .
'END',
'DELIMITER ;',
'-- --------------------------------------------------------' . "\n" .
'' . "\n" .
'--' . "\n" .
'-- Table structure for `actor`' . "\n" .
'--' . "\n" .
'' . "\n" .
'/* C-like comment syntax. */' . "\n" .
'CREATE TABLE IF NOT EXISTS `actor` (' . "\n" .
'`actor_id` SMALLINT(5) UNSIGNED NOT NULL,' . "\n" .
'`first_name` VARCHAR(45) NOT NULL,' . "\n" .
'`last_name` VARCHAR(45) NOT NULL,' . "\n" .
'`last_update` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP' . "\n" .
') ENGINE=InnoDB DEFAULT CHARSET=utf8',
'/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */',
'/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */',
'/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */',
],
],
[
$query,
64,
[
'parse_delimiter' => false,
'add_delimiter' => true,
],
[
'/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;',
'/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;',
'/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;',
'/*!40101 SET NAMES utf8mb4 */;',
'SET SQL_MODE = \'NO_AUTO_VALUE_ON_ZERO\';',
'SET time_zone = "+00:00";',
'# Bash-like comment sytanx.' . "\n" .
'CREATE DEFINER=`root`@`localhost` PROCEDURE `film_in_stock` (IN `p_film_id` ' .
'INT, IN `p_store_id` INT, OUT `p_film_count` INT) READS SQL DATA' . "\n" .
'BEGIN' . "\n" .
' SELECT inventory_id' . "\n" .
' FROM inventory' . "\n" .
' WHERE film_id = p_film_id' . "\n" .
' AND store_id = p_store_id' . "\n" .
' AND inventory_in_stock(inventory_id);' . "\n" .
'' . "\n" .
' SELECT FOUND_ROWS() INTO p_film_count;' . "\n" .
'END$$',
'-- --------------------------------------------------------' . "\n" .
'' . "\n" .
'--' . "\n" .
'-- Table structure for `actor`' . "\n" .
'--' . "\n" .
'' . "\n" .
'/* C-like comment syntax. */' . "\n" .
'CREATE TABLE IF NOT EXISTS `actor` (' . "\n" .
'`actor_id` SMALLINT(5) UNSIGNED NOT NULL,' . "\n" .
'`first_name` VARCHAR(45) NOT NULL,' . "\n" .
'`last_name` VARCHAR(45) NOT NULL,' . "\n" .
'`last_update` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP' . "\n" .
') ENGINE=InnoDB DEFAULT CHARSET=utf8;',
'/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;',
'/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;',
'/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */',
],
],
];
}
}
@@ -0,0 +1,482 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Utils;
use PhpMyAdmin\SqlParser\Tests\TestCase;
use PhpMyAdmin\SqlParser\Utils\CLI;
use function dirname;
use function exec;
use const PHP_BINARY;
class CLITest extends TestCase
{
/**
* @param array<string, bool|string>|false $getopt
*/
private function getCLI($getopt): CLI
{
$cli = $this->createPartialMock(CLI::class, ['getopt']);
$cli->method('getopt')->willReturn($getopt);
return $cli;
}
/**
* @param array<string, bool|string>|false $getopt
*/
private function getCLIStdIn(string $input, $getopt): CLI
{
$cli = $this->createPartialMock(CLI::class, ['getopt', 'readStdin']);
$cli->method('getopt')->willReturn($getopt);
$cli->method('readStdin')->willReturn($input);
return $cli;
}
/**
* Test that getopt call works.
*
* We do mock it for other tests to return values we want.
*/
public function testGetopt(): void
{
$cli = new CLI();
$this->assertEquals(
$cli->getopt('', []),
[]
);
}
/**
* @param array<string, bool|string>|false $getopt
*
* @dataProvider highlightParamsProvider
*/
public function testRunHighlight($getopt, string $output, int $result): void
{
$cli = $this->getCLI($getopt);
$this->expectOutputString($output);
$this->assertEquals($result, $cli->runHighlight());
}
/**
* @return array<int, array<int, int|string|array<string, bool|string>|false>>
* @psalm-return list<array{(array<string, bool|string>|false), string, int}>
*/
public static function highlightParamsProvider(): array
{
return [
[
['q' => 'SELECT 1'],
"\x1b[35mSELECT\n \x1b[92m1\x1b[0m\n",
0,
],
[
['query' => 'SELECT 1'],
"\x1b[35mSELECT\n \x1b[92m1\x1b[0m\n",
0,
],
[
[
'q' => 'SELECT /* comment */ 1 /* other */',
'f' => 'text',
],
"SELECT\n /* comment */ 1 /* other */\n",
0,
],
[
[
'q' => 'SELECT 1',
'f' => 'foo',
],
"ERROR: Invalid value for format!\n",
1,
],
[
[
'q' => 'SELECT 1',
'f' => 'html',
],
'<span class="sql-reserved">SELECT</span><br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;<span class="sql-number">1</span>' . "\n",
0,
],
[
['h' => true],
'Usage: highlight-query --query SQL [--format html|cli|text] [--ansi]' . "\n" .
' cat file.sql | highlight-query' . "\n",
0,
],
[
[],
'ERROR: Missing parameters!' . "\n" .
'Usage: highlight-query --query SQL [--format html|cli|text] [--ansi]' . "\n" .
' cat file.sql | highlight-query' . "\n",
1,
],
[
false,
'',
1,
],
];
}
/**
* @param array<string, bool|string>|false $getopt
*
* @dataProvider highlightParamsStdInProvider
*/
public function testRunHighlightStdIn(string $input, $getopt, string $output, int $result): void
{
$cli = $this->getCLIStdIn($input, $getopt);
$this->expectOutputString($output);
$this->assertEquals($result, $cli->runHighlight());
}
/**
* @return array<int, array<int, int|string|array<string, bool|string>|false>>
* @psalm-return list<array{string, (array<string, bool|string>|false), string, int}>
*/
public static function highlightParamsStdInProvider(): array
{
return [
[
'SELECT 1',
[],
"\x1b[35mSELECT\n \x1b[92m1\x1b[0m\n",
0,
],
[
'SELECT /* comment */ 1 /* other */',
['f' => 'text'],
"SELECT\n /* comment */ 1 /* other */\n",
0,
],
[
'SELECT 1',
['f' => 'foo'],
"ERROR: Invalid value for format!\n",
1,
],
[
'SELECT 1',
['f' => 'html'],
'<span class="sql-reserved">SELECT</span><br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;<span class="sql-number">1</span>' . "\n",
0,
],
[
'',
['h' => true],
'Usage: highlight-query --query SQL [--format html|cli|text] [--ansi]' . "\n" .
' cat file.sql | highlight-query' . "\n",
0,
],
[
'',
[],
'ERROR: Missing parameters!' . "\n" .
'Usage: highlight-query --query SQL [--format html|cli|text] [--ansi]' . "\n" .
' cat file.sql | highlight-query' . "\n",
1,
],
[
'',
false,
'',
1,
],
];
}
/**
* @param array<string, bool|string>|false $getopt
*
* @dataProvider lintParamsStdInProvider
*/
public function testRunLintFromStdIn(string $input, $getopt, string $output, int $result): void
{
$cli = $this->getCLIStdIn($input, $getopt);
$this->expectOutputString($output);
$this->assertEquals($result, $cli->runLint());
}
/**
* @return array<int, array<int, int|string|array<string, bool|string>|false>>
* @psalm-return list<array{string, (array<string, bool|string>|false), string, int}>
*/
public static function lintParamsStdInProvider(): array
{
return [
[
'SELECT 1',
[],
'',
0,
],
[
'SELECT SELECT',
[],
'#1: An expression was expected. (near "SELECT" at position 7)' . "\n" .
'#2: This type of clause was previously parsed. (near "SELECT" at position 7)' . "\n" .
'#3: An expression was expected. (near "" at position 0)' . "\n",
10,
],
[
'SELECT SELECT',
['c' => 'MySql80000'],
'#1: An expression was expected. (near "SELECT" at position 7)' . "\n" .
'#2: This type of clause was previously parsed. (near "SELECT" at position 7)' . "\n" .
'#3: An expression was expected. (near "" at position 0)' . "\n",
10,
],
[
'',
[],
'ERROR: Missing parameters!' . "\n" .
'Usage: lint-query --query SQL [--ansi]' . "\n" .
' cat file.sql | lint-query' . "\n",
1,
],
[
'',
['h' => true],
'Usage: lint-query --query SQL [--ansi]' . "\n" .
' cat file.sql | lint-query' . "\n",
0,
],
[
'',
false,
'',
1,
],
];
}
/**
* @param array<string, bool|string>|false $getopt
*
* @dataProvider lintParamsProvider
*/
public function testRunLint($getopt, string $output, int $result): void
{
$cli = $this->getCLI($getopt);
$this->expectOutputString($output);
$this->assertEquals($result, $cli->runLint());
}
/**
* @return array<int, array<int, int|string|array<string, bool|string>|false>>
* @psalm-return list<array{(array<string, bool|string>|false), string, int}>
*/
public static function lintParamsProvider(): array
{
return [
[
['q' => 'SELECT 1'],
'',
0,
],
[
['query' => 'SELECT 1'],
'',
0,
],
[
['q' => 'SELECT SELECT'],
'#1: An expression was expected. (near "SELECT" at position 7)' . "\n" .
'#2: This type of clause was previously parsed. (near "SELECT" at position 7)' . "\n" .
'#3: An expression was expected. (near "" at position 0)' . "\n",
10,
],
[
[
'q' => 'SELECT SELECT',
'c' => 'MySql80000',
],
'#1: An expression was expected. (near "SELECT" at position 7)' . "\n" .
'#2: This type of clause was previously parsed. (near "SELECT" at position 7)' . "\n" .
'#3: An expression was expected. (near "" at position 0)' . "\n",
10,
],
[
['h' => true],
'Usage: lint-query --query SQL [--ansi]' . "\n" .
' cat file.sql | lint-query' . "\n",
0,
],
[
[],
'ERROR: Missing parameters!' . "\n" .
'Usage: lint-query --query SQL [--ansi]' . "\n" .
' cat file.sql | lint-query' . "\n",
1,
],
[
false,
'',
1,
],
];
}
/**
* @param array<string, bool|string>|false $getopt
*
* @dataProvider tokenizeParamsProvider
*/
public function testRunTokenize($getopt, string $output, int $result): void
{
$cli = $this->getCLI($getopt);
$this->expectOutputString($output);
$this->assertEquals($result, $cli->runTokenize());
}
/**
* @return array<int, array<int, int|string|array<string, bool|string>|false>>
* @psalm-return list<array{(array<string, bool|string>|false), string, int}>
*/
public static function tokenizeParamsProvider(): array
{
$result = "[TOKEN 0]\nType = 1\nFlags = 3\nValue = 'SELECT'\nToken = 'SELECT'\n\n"
. "[TOKEN 1]\nType = 3\nFlags = 0\nValue = ' '\nToken = ' '\n\n"
. "[TOKEN 2]\nType = 6\nFlags = 0\nValue = 1\nToken = '1'\n\n"
. "[TOKEN 3]\nType = 9\nFlags = 0\nValue = NULL\nToken = NULL\n\n";
return [
[
['q' => 'SELECT 1'],
$result,
0,
],
[
['query' => 'SELECT 1'],
$result,
0,
],
[
['h' => true],
'Usage: tokenize-query --query SQL [--ansi]' . "\n" .
' cat file.sql | tokenize-query' . "\n",
0,
],
[
[],
'ERROR: Missing parameters!' . "\n" .
'Usage: tokenize-query --query SQL [--ansi]' . "\n" .
' cat file.sql | tokenize-query' . "\n",
1,
],
[
false,
'',
1,
],
];
}
/**
* @param array<string, bool|string>|false $getopt
*
* @dataProvider tokenizeParamsStdInProvider
*/
public function testRunTokenizeStdIn(string $input, $getopt, string $output, int $result): void
{
$cli = $this->getCLIStdIn($input, $getopt);
$this->expectOutputString($output);
$this->assertEquals($result, $cli->runTokenize());
}
/**
* @return array<int, array<int, int|string|array<string, bool|string>|false>>
* @psalm-return list<array{string, (array<string, bool|string>|false), string, int}>
*/
public static function tokenizeParamsStdInProvider(): array
{
$result = "[TOKEN 0]\nType = 1\nFlags = 3\nValue = 'SELECT'\nToken = 'SELECT'\n\n"
. "[TOKEN 1]\nType = 3\nFlags = 0\nValue = ' '\nToken = ' '\n\n"
. "[TOKEN 2]\nType = 6\nFlags = 0\nValue = 1\nToken = '1'\n\n"
. "[TOKEN 3]\nType = 9\nFlags = 0\nValue = NULL\nToken = NULL\n\n";
return [
[
'SELECT 1',
[],
$result,
0,
],
[
'',
['h' => true],
'Usage: tokenize-query --query SQL [--ansi]' . "\n" .
' cat file.sql | tokenize-query' . "\n",
0,
],
[
'',
[],
'ERROR: Missing parameters!' . "\n" .
'Usage: tokenize-query --query SQL [--ansi]' . "\n" .
' cat file.sql | tokenize-query' . "\n",
1,
],
[
'',
false,
'',
1,
],
];
}
/**
* @dataProvider stdinParamsProvider
*/
public function testStdinPipe(string $cmd, int $result): void
{
exec($cmd, $out, $ret);
$this->assertSame($result, $ret);
}
/**
* @return array<int, array<int, int|string>>
* @psalm-return list<array{string, int}>
*/
public static function stdinParamsProvider(): array
{
$binPath = PHP_BINARY . ' ' . dirname(__DIR__, 2) . '/bin/';
return [
[
'echo "SELECT 1" | ' . $binPath . 'highlight-query',
0,
],
[
'echo "invalid query" | ' . $binPath . 'highlight-query',
0,
],
[
'echo "SELECT 1" | ' . $binPath . 'lint-query',
0,
],
[
'echo "invalid query" | ' . $binPath . 'lint-query',
10,
],
[
'echo "SELECT 1" | ' . $binPath . 'tokenize-query',
0,
],
[
'echo "invalid query" | ' . $binPath . 'tokenize-query',
0,
],
];
}
}
@@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Utils;
use PhpMyAdmin\SqlParser\Lexer;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
use PhpMyAdmin\SqlParser\Utils\Error;
class ErrorTest extends TestCase
{
public function testGet(): void
{
$lexer = new Lexer('SELECT * FROM db..tbl }');
$parser = new Parser($lexer->list);
$this->assertEquals(
[
[
'Unexpected character.',
0,
'}',
22,
],
[
'Unexpected dot.',
0,
'.',
17,
],
],
Error::get([$lexer, $parser])
);
}
public function testGetWithNullToken(): void
{
$lexer = new Lexer('LOCK TABLES table1 AS `t1` LOCAL');
$parser = new Parser($lexer->list);
$this->assertSame(
[
['An alias was previously found.', 0, 'LOCAL', 27],
['Unexpected keyword.', 0, 'LOCAL', 27],
['Unexpected end of LOCK expression.', 0, '', null],
],
Error::get([$lexer, $parser])
);
}
public function testFormat(): void
{
$this->assertEquals(
['#1: error msg (near "token" at position 100)'],
Error::format([['error msg', 42, 'token', 100]])
);
$this->assertEquals(
[
'#1: error msg (near "token" at position 100)',
'#2: error msg (near "token" at position 200)',
],
Error::format([['error msg', 42, 'token', 100], ['error msg', 42, 'token', 200]])
);
}
}
@@ -0,0 +1,613 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Utils;
use PhpMyAdmin\SqlParser\Tests\TestCase;
use PhpMyAdmin\SqlParser\Utils\Formatter;
use ReflectionMethod;
class FormatterTest extends TestCase
{
/**
* @param array<int, array<string, int|string>> $default
* @param array<int, array<string, int|string>> $overriding
* @param array<int, array<string, int|string>> $expected
* @psalm-param list<array{type: int, flags: int, html: string, cli: string, function?: string}> $default
* @psalm-param list<array{type?: int, flags?: int, html?: string, cli?: string, function?: string}> $overriding
* @psalm-param list<array{type: int, flags: int, html: string, cli: string, function?: string}> $expected
*
* @dataProvider mergeFormatsProvider
*/
public function testMergeFormats(array $default, array $overriding, array $expected): void
{
$formatter = $this->createPartialMock(Formatter::class, ['getDefaultOptions', 'getDefaultFormats']);
$formatter->expects($this->once())
->method('getDefaultOptions')
->willReturn([
'type' => 'text',
'line_ending' => null,
'indentation' => null,
'clause_newline' => null,
'parts_newline' => null,
]);
$formatter->expects($this->once())
->method('getDefaultFormats')
->willReturn($default);
$expectedOptions = [
'type' => 'test-type',
'line_ending' => '<br>',
'indentation' => ' ',
'clause_newline' => null,
'parts_newline' => 0,
'formats' => $expected,
];
$overridingOptions = [
'type' => 'test-type',
'line_ending' => '<br>',
'formats' => $overriding,
];
$reflectionMethod = new ReflectionMethod($formatter, 'getMergedOptions');
$reflectionMethod->setAccessible(true);
$this->assertEquals($expectedOptions, $reflectionMethod->invoke($formatter, $overridingOptions));
}
/**
* @return array<string, array<string, array<int, array<string, int|string>>>>
* @psalm-return array<string, array{
* default: list<array{type: int, flags: int, html: string, cli: string, function?: string}>,
* overriding: list<array{type?: int, flags?: int, html?: string, cli?: string, function?: string}>,
* expected: list<array{type: int, flags: int, html: string, cli: string, function?: string}>
* }>
*/
public static function mergeFormatsProvider(): array
{
// [default[], overriding[], expected[]]
return [
'empty formats' => [
'default' => [
[
'type' => 0,
'flags' => 0,
'html' => '',
'cli' => '',
'function' => '',
],
],
'overriding' => [
[],
],
'expected' => [
[
'type' => 0,
'flags' => 0,
'html' => '',
'cli' => '',
'function' => '',
],
],
],
'no flags' => [
'default' => [
[
'type' => 0,
'flags' => 0,
'html' => 'html',
'cli' => 'cli',
],
[
'type' => 0,
'flags' => 1,
'html' => 'html',
'cli' => 'cli',
],
],
'overriding' => [
[
'type' => 0,
'html' => 'new html',
'cli' => 'new cli',
],
],
'expected' => [
[
'type' => 0,
'flags' => 0,
'html' => 'new html',
'cli' => 'new cli',
'function' => '',
],
[
'type' => 0,
'flags' => 1,
'html' => 'html',
'cli' => 'cli',
],
],
],
'with flags' => [
'default' => [
[
'type' => -1,
'flags' => 0,
'html' => 'html',
'cli' => 'cli',
],
[
'type' => 0,
'flags' => 0,
'html' => 'html',
'cli' => 'cli',
],
[
'type' => 0,
'flags' => 1,
'html' => 'html',
'cli' => 'cli',
],
],
'overriding' => [
[
'type' => 0,
'flags' => 0,
'html' => 'new html',
'cli' => 'new cli',
],
],
'expected' => [
[
'type' => -1,
'flags' => 0,
'html' => 'html',
'cli' => 'cli',
],
[
'type' => 0,
'flags' => 0,
'html' => 'new html',
'cli' => 'new cli',
'function' => '',
],
[
'type' => 0,
'flags' => 1,
'html' => 'html',
'cli' => 'cli',
],
],
],
'with extra formats' => [
'default' => [
[
'type' => 0,
'flags' => 0,
'html' => 'html',
'cli' => 'cli',
],
],
'overriding' => [
[
'type' => 0,
'flags' => 1,
'html' => 'new html',
'cli' => 'new cli',
],
[
'type' => 1,
'html' => 'new html',
'cli' => 'new cli',
],
[
'type' => 1,
'flags' => 1,
'html' => 'new html',
'cli' => 'new cli',
],
],
'expected' => [
[
'type' => 0,
'flags' => 0,
'html' => 'html',
'cli' => 'cli',
],
[
'type' => 0,
'flags' => 1,
'html' => 'new html',
'cli' => 'new cli',
'function' => '',
],
[
'type' => 1,
'flags' => 0,
'html' => 'new html',
'cli' => 'new cli',
'function' => '',
],
[
'type' => 1,
'flags' => 1,
'html' => 'new html',
'cli' => 'new cli',
'function' => '',
],
],
],
];
}
/**
* @param array<string, bool> $options
*
* @dataProvider formatQueriesProviders
*/
public function testFormat(string $query, string $text, string $cli, string $html, array $options = []): void
{
// Test TEXT format
$this->assertEquals(
$text,
Formatter::format($query, ['type' => 'text'] + $options),
'Text formatting failed.'
);
// Test CLI format
$this->assertEquals(
$cli,
Formatter::format($query, ['type' => 'cli'] + $options),
'CLI formatting failed.'
);
// Test HTML format
$this->assertEquals(
$html,
Formatter::format($query, ['type' => 'html'] + $options),
'HTML formatting failed.'
);
}
/**
* @return array<string, array<string, string|array<string, string|bool>>>
* @psalm-return array<string, array{
* query: string,
* text: string,
* cli: string,
* html: string,
* options?: array<string, bool>
* }>
*/
public static function formatQueriesProviders(): array
{
return [
'empty' => [
'query' => '',
'text' => '',
'cli' => "\x1b[0m",
'html' => '',
],
'minimal' => [
'query' => 'select 1',
'text' => 'SELECT' . "\n" .
' 1',
'cli' => "\x1b[35mSELECT\n" .
" \x1b[92m1\x1b[0m",
'html' => '<span class="sql-reserved">SELECT</span><br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;<span class="sql-number">1</span>',
],
'simply' => [
'query' => 'select * from tbl where 1',
'text' => 'SELECT' . "\n" .
' *' . "\n" .
'FROM' . "\n" .
' tbl' . "\n" .
'WHERE' . "\n" .
' 1',
'cli' => "\x1b[35mSELECT\n" .
" \x1b[39m*\n" .
"\x1b[35mFROM\n" .
" \x1b[39mtbl\n" .
"\x1b[35mWHERE\n" .
" \x1b[92m1\x1b[0m",
'html' => '<span class="sql-reserved">SELECT</span><br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;*<br/>' .
'<span class="sql-reserved">FROM</span><br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;tbl<br/>' .
'<span class="sql-reserved">WHERE</span><br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;<span class="sql-number">1</span>',
],
'typical' => [
'query' => 'SELECT id, if(id=1,"Si","No") from `tbl` where id = 0 or ' .
'id = 1 group by id order by id desc limit 1 offset 0',
'text' => 'SELECT' . "\n" .
' id,' . "\n" .
' IF(id = 1, "Si", "No")' . "\n" .
'FROM' . "\n" .
' `tbl`' . "\n" .
'WHERE' . "\n" .
' id = 0 OR id = 1' . "\n" .
'GROUP BY' . "\n" .
' id' . "\n" .
'ORDER BY' . "\n" .
' id' . "\n" .
'DESC' . "\n" .
'LIMIT 1 OFFSET 0',
'cli' => "\x1b[35mSELECT\n" .
" \x1b[39mid,\n" .
" \x1b[35mIF\x1b[39m(id = \x1b[92m1\x1b[39m, \x1b[91m\"Si\"\x1b[39m, \x1b[91m\"No\"\x1b[39m)\n" .
"\x1b[35mFROM\n" .
" \x1b[36m`tbl`\n" .
"\x1b[35mWHERE\n" .
" \x1b[39mid = \x1b[92m0 \x1b[35mOR \x1b[39mid = \x1b[92m1\n" .
"\x1b[35mGROUP BY\n" .
" \x1b[39mid\n" .
"\x1b[35mORDER BY\n" .
" \x1b[39mid\n" .
"\x1b[35mDESC\n" .
"LIMIT \x1b[92m1 \x1b[95mOFFSET \x1b[92m0\x1b[0m",
'html' => '<span class="sql-reserved">SELECT</span><br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;id,<br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;<span class="sql-reserved">IF</span>(id = ' .
'<span class="sql-number">1</span>, <span class="sql-string">"Si"</span>, ' .
'<span class="sql-string">"No"</span>)<br/>' .
'<span class="sql-reserved">FROM</span><br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;<span class="sql-variable">`tbl`</span><br/>' .
'<span class="sql-reserved">WHERE</span><br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;id = <span class="sql-number">0</span> ' .
'<span class="sql-reserved">OR</span> id = <span class="sql-number">1</span><br/>' .
'<span class="sql-reserved">GROUP BY</span><br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;id<br/>' .
'<span class="sql-reserved">ORDER BY</span><br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;id<br/>' .
'<span class="sql-reserved">DESC</span><br/>' .
'<span class="sql-reserved">LIMIT</span> <span class="sql-number">1</span> ' .
'<span class="sql-keyword">OFFSET</span> <span class="sql-number">0</span>',
],
'comments' => [
'query' => 'select /* Comment */ *' . "\n" .
'from tbl # Comment' . "\n" .
'where 1 -- Comment',
'text' => 'SELECT' . "\n" .
' /* Comment */ *' . "\n" .
'FROM' . "\n" .
' tbl # Comment' . "\n" .
'WHERE' . "\n" .
' 1 -- Comment',
'cli' => "\x1b[35mSELECT\n" .
" \x1b[37m/* Comment */ \x1b[39m*\n" .
"\x1b[35mFROM\n" .
" \x1b[39mtbl \x1b[37m# Comment\n" .
"\x1b[35mWHERE\n" .
" \x1b[92m1 \x1b[37m-- Comment\x1b[0m",
'html' => '<span class="sql-reserved">SELECT</span><br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;<span class="sql-comment">/* Comment */</span> *<br/>' .
'<span class="sql-reserved">FROM</span><br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;tbl <span class="sql-comment"># Comment</span><br/>' .
'<span class="sql-reserved">WHERE</span><br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;<span class="sql-number">1</span> ' .
'<span class="sql-comment">-- Comment</span>',
],
'strip comments' => [
'query' => 'select /* Comment */ *' . "\n" .
'from tbl # Comment' . "\n" .
'where 1 -- Comment',
'text' => 'SELECT' . "\n" .
' *' . "\n" .
'FROM' . "\n" .
' tbl' . "\n" .
'WHERE' . "\n" .
' 1',
'cli' => "\x1b[35mSELECT\n" .
" \x1b[39m*\n" .
"\x1b[35mFROM\n" .
" \x1b[39mtbl\n" .
"\x1b[35mWHERE\n" .
" \x1b[92m1\x1b[0m",
'html' => '<span class="sql-reserved">SELECT</span><br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;*<br/>' .
'<span class="sql-reserved">FROM</span><br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;tbl<br/>' .
'<span class="sql-reserved">WHERE</span><br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;<span class="sql-number">1</span>',
'options' => ['remove_comments' => true],
],
'keywords' => [
'query' => 'select hex("1")',
'text' => 'SELECT' . "\n" .
' HEX("1")',
'cli' => "\x1b[35mSELECT\n" .
" \x1b[95mHEX\x1b[39m(\x1b[91m\"1\"\x1b[39m)\x1b[0m",
'html' => '<span class="sql-reserved">SELECT</span><br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;<span class="sql-keyword">HEX</span>(<span class="sql-string">"1"</span>)',
],
'distinct count' => [
'query' => 'select distinct count(*)',
'text' => 'SELECT DISTINCT' . "\n" .
' COUNT(*)',
'cli' => "\x1b[35mSELECT DISTINCT\n" .
" \x1b[95mCOUNT\x1b[39m(*)\x1b[0m",
'html' => '<span class="sql-reserved">SELECT</span> <span class="sql-reserved">DISTINCT</span><br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;<span class="sql-keyword">COUNT</span>(*)',
],
'create procedure' => [
'query' => 'create procedure test_procedure() begin from tbl select *; end',
'text' => 'CREATE PROCEDURE test_procedure()' . "\n" .
'BEGIN' . "\n" .
' FROM' . "\n" .
' tbl' . "\n" .
' SELECT' . "\n" .
' *;' . "\n" .
'END',
'cli' => "\x1b[35mCREATE PROCEDURE \x1b[39mtest_procedure()\n" .
"\x1b[95mBEGIN\n" .
" \x1b[35mFROM\n" .
" \x1b[39mtbl\n" .
" \x1b[35mSELECT\n" .
" \x1b[39m*;\n" .
"\x1b[95mEND\x1b[0m",
'html' => '<span class="sql-reserved">CREATE</span> ' .
'<span class="sql-reserved">PROCEDURE</span> test_procedure()<br/>' .
'<span class="sql-keyword">BEGIN</span><br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;<span class="sql-reserved">FROM</span><br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tbl<br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;<span class="sql-reserved">SELECT</span><br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*;<br/>' .
'<span class="sql-keyword">END</span>',
],
'insert' => [
'query' => 'insert into foo values (0, 0, 0), (1, 1, 1)',
'text' => 'INSERT INTO foo' . "\n" .
'VALUES(0, 0, 0),(1, 1, 1)',
'cli' => "\x1b[35mINSERT INTO \x1b[39mfoo\n" .
"\x1b[35mVALUES\x1b[39m(\x1b[92m0\x1b[39m, \x1b[92m0\x1b[39m, " .
"\x1b[92m0\x1b[39m),(\x1b[92m1\x1b[39m, \x1b[92m1\x1b[39m, \x1b[92m1\x1b[39m)\x1b[0m",
'html' => '<span class="sql-reserved">INSERT</span> <span class="sql-reserved">INTO</span> foo<br/>' .
'<span class="sql-reserved">VALUES</span>(<span class="sql-number">0</span>, ' .
'<span class="sql-number">0</span>, <span class="sql-number">0</span>),(' .
'<span class="sql-number">1</span>, <span class="sql-number">1</span>, ' .
'<span class="sql-number">1</span>)',
],
'string as alias' => [
'query' => 'select "Text" as bar',
'text' => 'SELECT' . "\n" .
' "Text" AS bar',
'cli' => "\x1b[35mSELECT\n" .
" \x1b[91m\"Text\" \x1b[35mAS \x1b[39mbar\x1b[0m",
'html' => '<span class="sql-reserved">SELECT</span><br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;<span class="sql-string">"Text"</span> ' .
'<span class="sql-reserved">AS</span> bar',
],
'escape cli' => [
'query' => "select 'text\x1b[33mcolor-inj'",
'text' => 'SELECT' . "\n" .
" 'text\x1B[33mcolor-inj'",
'cli' => "\x1b[35mSELECT\n" .
" \x1b[91m'text\\x1B[33mcolor-inj'\x1b[0m",
'html' => '<span class="sql-reserved">SELECT</span><br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;<span class="sql-string">\'text' . "\x1b[33m" . 'color-inj\'</span>',
],
'escape html' => [
'query' => "select '<s>xss' from `<s>xss` , <s>nxss /*s<s>xss*/",
'text' => 'SELECT' . "\n" .
' \'<s>xss\'' . "\n" .
'FROM' . "\n" .
' `<s>xss`,' . "\n" .
' < s > nxss /*s<s>xss*/',
'cli' => "\x1b[35mSELECT\n" .
" \x1b[91m'<s>xss'\n" .
"\x1b[35mFROM\n" .
" \x1b[36m`<s>xss`\x1b[39m,\n" .
" < s > nxss \x1b[37m/*s<s>xss*/\x1b[0m",
'html' => '<span class="sql-reserved">SELECT</span><br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;<span class="sql-string">\'&lt;s&gt;xss\'</span><br/>' .
'<span class="sql-reserved">FROM</span><br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;<span class="sql-variable">`&lt;s&gt;xss`</span>,' .
'<br/>&nbsp;&nbsp;&nbsp;&nbsp;&lt; s &gt; nxss <span class="sql-comment">/*s&lt;s&gt;xss*/</span>',
],
'create table' => [
'query' => 'create table if not exists `pma__bookmark` (' . "\n" .
'`id` int(11) not null auto_increment,' . "\n" .
'`dbase` varchar(255) not null default "",' . "\n" .
'`user` varchar(255) not null default "",' . "\n" .
'`label` varchar(255) collate utf8_general_ci not null default "",' . "\n" .
'`query` text not null,' . "\n" .
'primary key (`id`)',
'text' => 'CREATE TABLE IF NOT EXISTS `pma__bookmark`(' . "\n" .
' `id` INT(11) NOT NULL AUTO_INCREMENT,' . "\n" .
' `dbase` VARCHAR(255) NOT NULL DEFAULT "",' . "\n" .
' `user` VARCHAR(255) NOT NULL DEFAULT "",' . "\n" .
' `label` VARCHAR(255) COLLATE utf8_general_ci NOT NULL DEFAULT "",' . "\n" .
' `query` TEXT NOT NULL,' . "\n" .
' PRIMARY KEY(`id`)',
'cli' => "\x1b[35mCREATE TABLE IF NOT EXISTS \x1b[36m`pma__bookmark`\x1b[39m(\n" .
" \x1b[36m`id` \x1b[35mINT\x1b[39m(\x1b[92m11\x1b[39m) " .
"\x1b[35mNOT NULL \x1b[95mAUTO_INCREMENT\x1b[39m,\n" .
" \x1b[36m`dbase` \x1b[35mVARCHAR\x1b[39m(\x1b[92m255\x1b[39m) " .
"\x1b[35mNOT NULL DEFAULT \x1b[91m\"\"\x1b[39m,\n" .
" \x1b[36m`user` \x1b[35mVARCHAR\x1b[39m(\x1b[92m255\x1b[39m) " .
"\x1b[35mNOT NULL DEFAULT \x1b[91m\"\"\x1b[39m,\n" .
" \x1b[36m`label` \x1b[35mVARCHAR\x1b[39m(\x1b[92m255\x1b[39m) " .
"\x1b[35mCOLLATE \x1b[39mutf8_general_ci \x1b[35mNOT NULL DEFAULT \x1b[91m\"\"\x1b[39m,\n" .
" \x1b[36m`query` \x1b[95mTEXT \x1b[35mNOT NULL\x1b[39m,\n" .
" \x1b[35mPRIMARY KEY\x1b[39m(\x1b[36m`id`\x1b[39m)\x1b[0m",
'html' => '<span class="sql-reserved">CREATE</span> <span class="sql-reserved">TABLE</span> ' .
'<span class="sql-reserved">IF NOT EXISTS</span> <span class="sql-variable">' .
'`pma__bookmark`</span>(<br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;<span class="sql-variable">`id`</span> ' .
'<span class="sql-reserved">INT</span>(<span class="sql-number">11</span>) ' .
'<span class="sql-reserved">NOT NULL</span> <span class="sql-keyword">AUTO_INCREMENT</span>,<br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;<span class="sql-variable">`dbase`</span> ' .
'<span class="sql-reserved">VARCHAR</span>(<span class="sql-number">255</span>) ' .
'<span class="sql-reserved">NOT NULL</span> <span class="sql-reserved">DEFAULT</span> ' .
'<span class="sql-string">""</span>,<br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;<span class="sql-variable">`user`</span> ' .
'<span class="sql-reserved">VARCHAR</span>(<span class="sql-number">255</span>) ' .
'<span class="sql-reserved">NOT NULL</span> <span class="sql-reserved">DEFAULT</span> ' .
'<span class="sql-string">""</span>,<br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;<span class="sql-variable">`label`</span> ' .
'<span class="sql-reserved">VARCHAR</span>(<span class="sql-number">255</span>) ' .
'<span class="sql-reserved">COLLATE</span> utf8_general_ci ' .
'<span class="sql-reserved">NOT NULL</span> <span class="sql-reserved">DEFAULT</span> ' .
'<span class="sql-string">""</span>,<br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;<span class="sql-variable">`query`</span> ' .
'<span class="sql-keyword">TEXT</span> <span class="sql-reserved">NOT NULL</span>,<br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;<span class="sql-reserved">' .
'PRIMARY KEY</span>(<span class="sql-variable">`id`</span>)',
],
'join' => [
'query' => 'join tbl2 on c1=c2',
'text' => 'JOIN tbl2 ON c1 = c2',
'cli' => "\x1b[35mJOIN \x1b[39mtbl2 \x1b[35mON \x1b[39mc1 = c2" .
"\x1b[0m",
'html' => '<span class="sql-reserved">JOIN</span> tbl2 <span class="sql-reserved">ON</span> c1 = c2',
],
'named param' => [
'query' => 'select * from tbl where col = :param',
'text' => 'SELECT' . "\n" .
' *' . "\n" .
'FROM' . "\n" .
' tbl' . "\n" .
'WHERE' . "\n" .
' col = :param',
'cli' => "\x1b[35mSELECT\n" .
" \x1b[39m*\n" .
"\x1b[35mFROM\n" .
" \x1b[39mtbl\n" .
"\x1b[35mWHERE\n" .
" \x1b[39mcol = \x1b[31m:param\x1b[0m",
'html' => '<span class="sql-reserved">SELECT</span><br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;*<br/>' .
'<span class="sql-reserved">FROM</span><br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;tbl<br/>' .
'<span class="sql-reserved">WHERE</span><br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;col = <span class="sql-parameter">:param</span>',
],
'anon param' => [
'query' => 'select * from tbl where col = ?',
'text' => 'SELECT' . "\n" .
' *' . "\n" .
'FROM' . "\n" .
' tbl' . "\n" .
'WHERE' . "\n" .
' col = ?',
'cli' => "\x1b[35mSELECT\n" .
" \x1b[39m*\n" .
"\x1b[35mFROM\n" .
" \x1b[39mtbl\n" .
"\x1b[35mWHERE\n" .
" \x1b[39mcol = \x1b[31m?\x1b[0m",
'html' => '<span class="sql-reserved">SELECT</span><br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;*<br/>' .
'<span class="sql-reserved">FROM</span><br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;tbl<br/>' .
'<span class="sql-reserved">WHERE</span><br/>' .
'&nbsp;&nbsp;&nbsp;&nbsp;col = <span class="sql-parameter">?</span>',
],
];
}
}
@@ -0,0 +1,137 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Utils;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
use PhpMyAdmin\SqlParser\Utils\Misc;
class MiscTest extends TestCase
{
/**
* @param mixed[] $expected
* @psalm-param array<string, array{
* alias: (string|null),
* tables: array<string, array{alias: (string|null), columns: array<string, string>}>
* }> $expected
*
* @dataProvider getAliasesProvider
*/
public function testGetAliases(string $query, ?string $db, array $expected): void
{
$parser = new Parser($query);
$statement = empty($parser->statements[0]) ?
null : $parser->statements[0];
$this->assertEquals($expected, Misc::getAliases($statement, $db));
}
/**
* @return array<int, array<int, string|mixed[]|null>>
* @psalm-return list<array{string, (string|null), array<string, array{
* alias: (string|null),
* tables: array<string, array{alias: (string|null), columns: array<string, string>}>
* }>}>
*/
public static function getAliasesProvider(): array
{
return [
[
'select * from (select 1) tbl',
'mydb',
[],
],
[
'select i.name as `n`,abcdef gh from qwerty i',
'mydb',
[
'mydb' => [
'alias' => null,
'tables' => [
'qwerty' => [
'alias' => 'i',
'columns' => [
'name' => 'n',
'abcdef' => 'gh',
],
],
],
],
],
],
[
'select film_id id,title from film',
'sakila',
[
'sakila' => [
'alias' => null,
'tables' => [
'film' => [
'alias' => null,
'columns' => ['film_id' => 'id'],
],
],
],
],
],
[
'select `sakila`.`A`.`actor_id` as aid,`F`.`film_id` `fid`,'
. 'last_update updated from `sakila`.actor A join `film_actor` as '
. '`F` on F.actor_id = A.`actor_id`',
'sakila',
[
'sakila' => [
'alias' => null,
'tables' => [
'film_actor' => [
'alias' => 'F',
'columns' => [
'film_id' => 'fid',
'last_update' => 'updated',
],
],
'actor' => [
'alias' => 'A',
'columns' => [
'actor_id' => 'aid',
'last_update' => 'updated',
],
],
],
],
],
],
[
'SELECT film_id FROM (SELECT * FROM film) as f;',
'sakila',
[],
],
[
'',
null,
[],
],
[
'SELECT 1',
null,
[],
],
[
'SELECT * FROM orders AS ord WHERE 1',
'db',
[
'db' => [
'alias' => null,
'tables' => [
'orders' => [
'alias' => 'ord',
'columns' => [],
],
],
],
],
],
];
}
}
@@ -0,0 +1,747 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Utils;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
use PhpMyAdmin\SqlParser\Utils\Query;
use function array_merge;
/**
* @psalm-import-type QueryFlagsType from Query
*/
class QueryTest extends TestCase
{
/**
* @param array<string, bool|string> $expected
* @psalm-param QueryFlagsType $expected
*
* @dataProvider getFlagsProvider
*/
public function testGetFlags(string $query, array $expected): void
{
$parser = new Parser($query);
$this->assertEquals($expected, Query::getFlags($parser->statements[0]));
}
/**
* @return array<int, array<int, string|array<string, bool|string>>>
* @psalm-return list<array{non-empty-string, QueryFlagsType}>
*/
public static function getFlagsProvider(): array
{
return [
[
'ALTER TABLE DROP col',
[
'reload' => true,
'querytype' => 'ALTER',
],
],
[
'CALL test()',
[
'is_procedure' => true,
'querytype' => 'CALL',
],
],
[
'CREATE TABLE tbl (id INT)',
[
'reload' => true,
'querytype' => 'CREATE',
],
],
[
'CHECK TABLE tbl',
[
'is_maint' => true,
'querytype' => 'CHECK',
],
],
[
'DELETE FROM tbl',
[
'is_affected' => true,
'is_delete' => true,
'querytype' => 'DELETE',
],
],
[
'DROP VIEW v',
[
'reload' => true,
'querytype' => 'DROP',
],
],
[
'DROP DATABASE db',
[
'drop_database' => true,
'reload' => true,
'querytype' => 'DROP',
],
],
[
'EXPLAIN tbl',
[
'is_explain' => true,
'querytype' => 'EXPLAIN',
],
],
[
'LOAD DATA INFILE \'/tmp/test.txt\' INTO TABLE test',
[
'is_affected' => true,
'is_insert' => true,
'querytype' => 'LOAD',
],
],
[
'INSERT INTO tbl VALUES (1)',
[
'is_affected' => true,
'is_insert' => true,
'querytype' => 'INSERT',
],
],
[
'REPLACE INTO tbl VALUES (2)',
[
'is_affected' => true,
'is_replace' => true,
'is_insert' => true,
'querytype' => 'REPLACE',
],
],
[
'SELECT 1',
[
'is_select' => true,
'querytype' => 'SELECT',
],
],
[
'SELECT * FROM tbl',
[
'is_select' => true,
'select_from' => true,
'querytype' => 'SELECT',
],
],
[
'SELECT DISTINCT * FROM tbl LIMIT 0, 10 ORDER BY id',
[
'distinct' => true,
'is_select' => true,
'select_from' => true,
'limit' => true,
'order' => true,
'querytype' => 'SELECT',
],
],
[
'SELECT * FROM actor GROUP BY actor_id',
[
'is_group' => true,
'is_select' => true,
'select_from' => true,
'group' => true,
'querytype' => 'SELECT',
],
],
[
'SELECT col1, col2 FROM table1 PROCEDURE ANALYSE(10, 2000);',
[
'is_analyse' => true,
'is_select' => true,
'select_from' => true,
'querytype' => 'SELECT',
],
],
[
'SELECT * FROM tbl INTO OUTFILE "/tmp/export.txt"',
[
'is_export' => true,
'is_select' => true,
'select_from' => true,
'querytype' => 'SELECT',
],
],
[
'SELECT COUNT(id), SUM(id) FROM tbl',
[
'is_count' => true,
'is_func' => true,
'is_select' => true,
'select_from' => true,
'querytype' => 'SELECT',
],
],
[
'SELECT count(*) FROM tbl',
[
'is_count' => true,
'is_select' => true,
'select_from' => true,
'querytype' => 'SELECT',
],
],
[
'SELECT sum(*) FROM tbl',
[
'is_func' => true,
'is_select' => true,
'select_from' => true,
'querytype' => 'SELECT',
],
],
[
'SELECT (SELECT "foo")',
[
'is_select' => true,
'is_subquery' => true,
'querytype' => 'SELECT',
],
],
[
'SELECT * FROM customer HAVING store_id = 2;',
[
'is_select' => true,
'select_from' => true,
'is_group' => true,
'having' => true,
'querytype' => 'SELECT',
],
],
[
'SELECT * FROM table1 INNER JOIN table2 ON table1.id=table2.id;',
[
'is_select' => true,
'select_from' => true,
'join' => true,
'querytype' => 'SELECT',
],
],
[
'SHOW CREATE TABLE tbl',
[
'is_show' => true,
'querytype' => 'SHOW',
],
],
[
'UPDATE tbl SET id = 1',
[
'is_affected' => true,
'querytype' => 'UPDATE',
],
],
[
'ANALYZE TABLE tbl',
[
'is_maint' => true,
'querytype' => 'ANALYZE',
],
],
[
'CHECKSUM TABLE tbl',
[
'is_maint' => true,
'querytype' => 'CHECKSUM',
],
],
[
'OPTIMIZE TABLE tbl',
[
'is_maint' => true,
'querytype' => 'OPTIMIZE',
],
],
[
'REPAIR TABLE tbl',
[
'is_maint' => true,
'querytype' => 'REPAIR',
],
],
[
'(SELECT a FROM t1 WHERE a=10 AND B=1 ORDER BY a LIMIT 10) ' .
'UNION ' .
'(SELECT a FROM t2 WHERE a=11 AND B=2 ORDER BY a LIMIT 10);',
[
'is_select' => true,
'select_from' => true,
'limit' => true,
'order' => true,
'union' => true,
'querytype' => 'SELECT',
],
],
[
'SELECT * FROM orders AS ord WHERE 1',
[
'querytype' => 'SELECT',
'is_select' => true,
'select_from' => true,
],
],
[
'SET NAMES \'latin\'',
['querytype' => 'SET'],
],
];
}
public function testGetAll(): void
{
$this->assertEquals(
[
'distinct' => false,
'drop_database' => false,
'group' => false,
'having' => false,
'is_affected' => false,
'is_analyse' => false,
'is_count' => false,
'is_delete' => false,
'is_explain' => false,
'is_export' => false,
'is_func' => false,
'is_group' => false,
'is_insert' => false,
'is_maint' => false,
'is_procedure' => false,
'is_replace' => false,
'is_select' => false,
'is_show' => false,
'is_subquery' => false,
'join' => false,
'limit' => false,
'offset' => false,
'order' => false,
'querytype' => false,
'reload' => false,
'select_from' => false,
'union' => false,
],
Query::getAll('')
);
$query = 'SELECT *, actor.actor_id, sakila2.film.*
FROM sakila2.city, sakila2.film, actor';
$parser = new Parser($query);
$this->assertEquals(
array_merge(
Query::getFlags($parser->statements[0], true),
[
'parser' => $parser,
'statement' => $parser->statements[0],
'select_expr' => ['*'],
'select_tables' => [
[
'actor',
null,
],
[
'film',
'sakila2',
],
],
]
),
Query::getAll($query)
);
$query = 'SELECT * FROM sakila.actor, film';
$parser = new Parser($query);
$this->assertEquals(
array_merge(
Query::getFlags($parser->statements[0], true),
[
'parser' => $parser,
'statement' => $parser->statements[0],
'select_expr' => ['*'],
'select_tables' => [
[
'actor',
'sakila',
],
[
'film',
null,
],
],
]
),
Query::getAll($query)
);
$query = 'SELECT a.actor_id FROM sakila.actor AS a, film';
$parser = new Parser($query);
$this->assertEquals(
array_merge(
Query::getFlags($parser->statements[0], true),
[
'parser' => $parser,
'statement' => $parser->statements[0],
'select_expr' => [],
'select_tables' => [
[
'actor',
'sakila',
],
],
]
),
Query::getAll($query)
);
$query = 'SELECT CASE WHEN 2 IS NULL THEN "this is true" ELSE "this is false" END';
$parser = new Parser($query);
$this->assertEquals(
array_merge(
Query::getFlags($parser->statements[0], true),
[
'parser' => $parser,
'statement' => $parser->statements[0],
'select_expr' => ['CASE WHEN 2 IS NULL THEN "this is true" ELSE "this is false" END'],
'select_tables' => [],
]
),
Query::getAll($query)
);
}
/**
* @param string[] $expected
*
* @dataProvider getTablesProvider
*/
public function testGetTables(string $query, array $expected): void
{
$parser = new Parser($query);
$this->assertEquals(
$expected,
Query::getTables($parser->statements[0])
);
}
/**
* @return array<int, array<int, string|string[]>>
* @psalm-return list<array{string, string[]}>
*/
public static function getTablesProvider(): array
{
return [
[
'INSERT INTO tbl(`id`, `name`) VALUES (1, "Name")',
['`tbl`'],
],
[
'INSERT INTO 0tbl(`id`, `name`) VALUES (1, "Name")',
['`0tbl`'],
],
[
'UPDATE tbl SET id = 0',
['`tbl`'],
],
[
'UPDATE 0tbl SET id = 0',
['`0tbl`'],
],
[
'DELETE FROM tbl WHERE id < 10',
['`tbl`'],
],
[
'DELETE FROM 0tbl WHERE id < 10',
['`0tbl`'],
],
[
'TRUNCATE tbl',
['`tbl`'],
],
[
'DROP VIEW v',
[],
],
[
'DROP TABLE tbl1, tbl2',
[
'`tbl1`',
'`tbl2`',
],
],
[
'RENAME TABLE a TO b, c TO d',
[
'`a`',
'`c`',
],
],
];
}
public function testGetClause(): void
{
/* Assertion 1 */
$parser = new Parser(
'SELECT c.city_id, c.country_id ' .
'FROM `city` ' .
'WHERE city_id < 1 /* test */' .
'ORDER BY city_id ASC ' .
'LIMIT 0, 1 ' .
'INTO OUTFILE "/dev/null"'
);
$this->assertEquals(
'0, 1 INTO OUTFILE "/dev/null"',
Query::getClause(
$parser->statements[0],
$parser->list,
'LIMIT',
0
)
);
// Assert it returns all clauses between FROM and LIMIT
$this->assertEquals(
'WHERE city_id < 1 ORDER BY city_id ASC',
Query::getClause(
$parser->statements[0],
$parser->list,
'FROM',
'LIMIT'
)
);
// Assert it returns all clauses between SELECT and LIMIT
$this->assertEquals(
'FROM `city` WHERE city_id < 1 ORDER BY city_id ASC',
Query::getClause(
$parser->statements[0],
$parser->list,
'LIMIT',
'SELECT'
)
);
/* Assertion 2 */
$parser = new Parser(
'DELETE FROM `renewal` ' .
'WHERE number = "1DB" AND actionDate <= CURRENT_DATE() ' .
'ORDER BY id ASC ' .
'LIMIT 1'
);
$this->assertEquals(
'number = "1DB" AND actionDate <= CURRENT_DATE()',
Query::getClause(
$parser->statements[0],
$parser->list,
'WHERE'
)
);
$this->assertEquals(
'1',
Query::getClause(
$parser->statements[0],
$parser->list,
'LIMIT'
)
);
$this->assertEquals(
'id ASC',
Query::getClause(
$parser->statements[0],
$parser->list,
'ORDER BY'
)
);
/* Assertion 3 */
$parser = new Parser(
'UPDATE `renewal` SET `some_column` = 1 ' .
'WHERE number = "1DB" AND actionDate <= CURRENT_DATE() ' .
'ORDER BY id ASC ' .
'LIMIT 1'
);
$this->assertEquals(
'number = "1DB" AND actionDate <= CURRENT_DATE()',
Query::getClause(
$parser->statements[0],
$parser->list,
'WHERE'
)
);
$this->assertEquals(
'1',
Query::getClause(
$parser->statements[0],
$parser->list,
'LIMIT'
)
);
$this->assertEquals(
'id ASC',
Query::getClause(
$parser->statements[0],
$parser->list,
'ORDER BY'
)
);
}
public function testReplaceClause(): void
{
$parser = new Parser('SELECT *, (SELECT 1) FROM film LIMIT 0, 10;');
$this->assertEquals(
'SELECT *, (SELECT 1) FROM film WHERE film_id > 0 LIMIT 0, 10',
Query::replaceClause(
$parser->statements[0],
$parser->list,
'WHERE film_id > 0'
)
);
$parser = new Parser(
'select supplier.city, supplier.id from supplier '
. 'union select customer.city, customer.id from customer'
);
$this->assertEquals(
'select supplier.city, supplier.id from supplier '
. 'union select customer.city, customer.id from customer'
. ' ORDER BY city ',
Query::replaceClause(
$parser->statements[0],
$parser->list,
'ORDER BY city'
)
);
$parser = new Parser('SELECT * FROM `t` FOR UPDATE');
$this->assertEquals(
'SELECT * FROM `t` LIMIT 0, 25 FOR UPDATE',
Query::replaceClause(
$parser->statements[0],
$parser->list,
'LIMIT 0, 25'
)
);
}
public function testReplaceClauseOnlyKeyword(): void
{
$parser = new Parser('SELECT *, (SELECT 1) FROM film LIMIT 0, 10');
$this->assertEquals(
' SELECT SQL_CALC_FOUND_ROWS *, (SELECT 1) FROM film LIMIT 0, 10',
Query::replaceClause(
$parser->statements[0],
$parser->list,
'SELECT SQL_CALC_FOUND_ROWS',
null,
true
)
);
}
public function testReplaceNonExistingPart(): void
{
$parser = new Parser('ALTER TABLE `sale_mast` OPTIMIZE PARTITION p3');
$this->assertEquals(
' ALTER TABLE `sale_mast` OPTIMIZE PARTITION p3',
Query::replaceClause(
$parser->statements[0],
$parser->list,
'ORDER BY',
''
)
);
}
public function testReplaceClauses(): void
{
$parser = new Parser('SELECT *, (SELECT 1) FROM film LIMIT 0, 10;');
$this->assertSame('', Query::replaceClauses($parser->statements[0], $parser->list, []));
$this->assertEquals(
'SELECT *, (SELECT 1) FROM film WHERE film_id > 0 LIMIT 0, 10',
Query::replaceClauses(
$parser->statements[0],
$parser->list,
[
[
'WHERE',
'WHERE film_id > 0',
],
]
)
);
$parser = new Parser(
'SELECT c.city_id, c.country_id ' .
'INTO OUTFILE "/dev/null" ' .
'FROM `city` ' .
'WHERE city_id < 1 ' .
'ORDER BY city_id ASC ' .
'LIMIT 0, 1 '
);
$this->assertEquals(
'SELECT c.city_id, c.country_id ' .
'INTO OUTFILE "/dev/null" ' .
'FROM city AS c ' .
'ORDER BY city_id ASC ' .
'LIMIT 0, 10 ',
Query::replaceClauses(
$parser->statements[0],
$parser->list,
[
[
'FROM',
'FROM city AS c',
],
[
'WHERE',
'',
],
[
'LIMIT',
'LIMIT 0, 10',
],
]
)
);
}
public function testGetFirstStatement(): void
{
$query = 'USE saki';
$delimiter = null;
[$statement, $query, $delimiter] = Query::getFirstStatement($query, $delimiter);
$this->assertNull($statement);
$this->assertEquals('USE saki', $query);
$query = 'USE sakila; ' .
'/*test comment*/' .
'SELECT * FROM actor; ' .
'DELIMITER $$ ' .
'UPDATE actor SET last_name = "abc"$$' .
'/*!SELECT * FROM actor WHERE last_name = "abc"*/$$';
$delimiter = null;
[$statement, $query, $delimiter] = Query::getFirstStatement($query, $delimiter);
$this->assertEquals('USE sakila;', $statement);
[$statement, $query, $delimiter] = Query::getFirstStatement($query, $delimiter);
$this->assertEquals('SELECT * FROM actor;', $statement);
[$statement, $query, $delimiter] = Query::getFirstStatement($query, $delimiter);
$this->assertEquals('DELIMITER $$', $statement);
$this->assertEquals('$$', $delimiter);
[$statement, $query, $delimiter] = Query::getFirstStatement($query, $delimiter);
$this->assertEquals('UPDATE actor SET last_name = "abc"$$', $statement);
[$statement, $query, $delimiter] = Query::getFirstStatement($query, $delimiter);
$this->assertEquals('SELECT * FROM actor WHERE last_name = "abc"$$', $statement);
}
}
@@ -0,0 +1,321 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Utils;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
use PhpMyAdmin\SqlParser\Utils\Routine;
class RoutineTest extends TestCase
{
/**
* @param string[] $expected
*
* @dataProvider getReturnTypeProvider
*/
public function testGetReturnType(string $def, array $expected): void
{
$this->assertEquals($expected, Routine::getReturnType($def));
}
/**
* @return array<int, array<int, string|array<int, string>>>
* @psalm-return list<array{string, string[]}>
*/
public static function getReturnTypeProvider(): array
{
return [
[
'',
[
'',
'',
'',
'',
'',
],
],
[
'TEXT',
[
'',
'',
'TEXT',
'',
'',
],
],
[
'INT(20)',
[
'',
'',
'INT',
'20',
'',
],
],
[
'INT UNSIGNED',
[
'',
'',
'INT',
'',
'UNSIGNED',
],
],
[
'VARCHAR(1) CHARSET utf8',
[
'',
'',
'VARCHAR',
'1',
'utf8',
],
],
[
'ENUM(\'a\', \'b\') CHARSET latin1',
[
'',
'',
'ENUM',
'\'a\',\'b\'',
'latin1',
],
],
[
'DECIMAL(5,2) UNSIGNED ZEROFILL',
[
'',
'',
'DECIMAL',
'5,2',
'UNSIGNED ZEROFILL',
],
],
[
'SET(\'test\'\'esc"\', \'more\\\'esc\')',
[
'',
'',
'SET',
'\'test\'\'esc"\',\'more\\\'esc\'',
'',
],
],
];
}
/**
* @param string[] $expected
*
* @dataProvider getParameterProvider
*/
public function testGetParameter(string $def, array $expected): void
{
$this->assertEquals($expected, Routine::getParameter($def));
}
/**
* @return array<int, array<int, string|array<int, string>>>
* @psalm-return list<array{string, string[]}>
*/
public static function getParameterProvider(): array
{
return [
[
'',
[
'',
'',
'',
'',
'',
],
],
[
'`foo` TEXT',
[
'',
'foo',
'TEXT',
'',
'',
],
],
[
'`foo` INT(20)',
[
'',
'foo',
'INT',
'20',
'',
],
],
[
'IN `fo``fo` INT UNSIGNED',
[
'IN',
'fo`fo',
'INT',
'',
'UNSIGNED',
],
],
[
'OUT bar VARCHAR(1) CHARSET utf8',
[
'OUT',
'bar',
'VARCHAR',
'1',
'utf8',
],
],
[
'`"baz\'\'` ENUM(\'a\', \'b\') CHARSET latin1',
[
'',
'"baz\'\'',
'ENUM',
'\'a\',\'b\'',
'latin1',
],
],
[
'INOUT `foo` DECIMAL(5,2) UNSIGNED ZEROFILL',
[
'INOUT',
'foo',
'DECIMAL',
'5,2',
'UNSIGNED ZEROFILL',
],
],
[
'`foo``s func` SET(\'test\'\'esc"\', \'more\\\'esc\')',
[
'',
'foo`s func',
'SET',
'\'test\'\'esc"\',\'more\\\'esc\'',
'',
],
],
];
}
/**
* @param array<string, int|string[]|string[][]> $expected
* @psalm-param array{
* num: int,
* dir: string[],
* name: string[],
* type: string[],
* length: string[],
* length_arr: string[][],
* opts: string[]
* } $expected
*
* @dataProvider getParametersProvider
*/
public function testGetParameters(string $query, array $expected): void
{
$parser = new Parser($query);
$this->assertEquals($expected, Routine::getParameters($parser->statements[0]));
}
/**
* @return array<int, array<int, string|array<string, int|string[]|string[][]>>>
* @psalm-return list<array{string, array{
* num: int,
* dir: string[],
* name: string[],
* type: string[],
* length: string[],
* length_arr: string[][],
* opts: string[]
* }}>
*/
public static function getParametersProvider(): array
{
return [
[
'CREATE PROCEDURE `foo`() SET @A=0',
[
'num' => 0,
'dir' => [],
'name' => [],
'type' => [],
'length' => [],
'length_arr' => [],
'opts' => [],
],
],
[
'CREATE DEFINER=`user\\`@`somehost``(` FUNCTION `foo```(`baz` INT) BEGIN SELECT NULL; END',
[
'num' => 1,
'dir' => [0 => ''],
'name' => [0 => 'baz'],
'type' => [0 => 'INT'],
'length' => [0 => ''],
'length_arr' => [
0 => [],
],
'opts' => [0 => ''],
],
],
[
'CREATE PROCEDURE `foo`(IN `baz\\)` INT(25) zerofill unsigned) BEGIN SELECT NULL; END',
[
'num' => 1,
'dir' => [0 => 'IN'],
'name' => [0 => 'baz\\)'],
'type' => [0 => 'INT'],
'length' => [0 => '25'],
'length_arr' => [
0 => ['25'],
],
'opts' => [0 => 'UNSIGNED ZEROFILL'],
],
],
[
'CREATE PROCEDURE `foo`(IN `baz\\` INT(001) zerofill, out bazz varchar(15) charset utf8) ' .
'BEGIN SELECT NULL; END',
[
'num' => 2,
'dir' => [
0 => 'IN',
1 => 'OUT',
],
'name' => [
0 => 'baz\\',
1 => 'bazz',
],
'type' => [
0 => 'INT',
1 => 'VARCHAR',
],
'length' => [
0 => '1',
1 => '15',
],
'length_arr' => [
0 => ['1'],
1 => ['15'],
],
'opts' => [
0 => 'ZEROFILL',
1 => 'utf8',
],
],
],
];
}
}
@@ -0,0 +1,262 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Utils;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Tests\TestCase;
use PhpMyAdmin\SqlParser\Utils\Table;
class TableTest extends TestCase
{
/**
* @param array<string, string|string[]|null>[] $expected
* @psalm-param list<array{
* constraint: string,
* index_list: string[],
* ref_db_name: null,
* ref_table_name: string,
* ref_index_list: string[],
* on_update: string,
* on_delete?: string
* }> $expected
*
* @dataProvider getForeignKeysProvider
*/
public function testGetForeignKeys(string $query, array $expected): void
{
$parser = new Parser($query);
$this->assertEquals($expected, Table::getForeignKeys($parser->statements[0]));
}
/**
* @return array<int, array<int, string|array<string, string|string[]|null>[]>>
* @psalm-return list<array{string, list<array{
* constraint: string,
* index_list: string[],
* ref_db_name: null,
* ref_table_name: string,
* ref_index_list: string[],
* on_update: string,
* on_delete?: string
* }>}>
*/
public static function getForeignKeysProvider(): array
{
return [
[
'CREATE USER test',
[],
],
[
'CREATE TABLE `payment` (
`payment_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
`customer_id` smallint(5) unsigned NOT NULL,
`staff_id` tinyint(3) unsigned NOT NULL,
`rental_id` int(11) DEFAULT NULL,
`amount` decimal(5,2) NOT NULL,
`payment_date` datetime NOT NULL,
`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`payment_id`),
KEY `idx_fk_staff_id` (`staff_id`),
KEY `idx_fk_customer_id` (`customer_id`),
KEY `fk_payment_rental` (`rental_id`),
CONSTRAINT `fk_payment_customer` FOREIGN KEY (`customer_id`)
REFERENCES `customer` (`customer_id`) ON UPDATE CASCADE,
CONSTRAINT `fk_payment_rental` FOREIGN KEY (`rental_id`)
REFERENCES `rental` (`rental_id`) ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT `fk_payment_staff` FOREIGN KEY (`staff_id`)
REFERENCES `staff` (`staff_id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=16050 DEFAULT CHARSET=utf8',
[
[
'constraint' => 'fk_payment_customer',
'index_list' => ['customer_id'],
'ref_db_name' => null,
'ref_table_name' => 'customer',
'ref_index_list' => ['customer_id'],
'on_update' => 'CASCADE',
],
[
'constraint' => 'fk_payment_rental',
'index_list' => ['rental_id'],
'ref_db_name' => null,
'ref_table_name' => 'rental',
'ref_index_list' => ['rental_id'],
'on_delete' => 'SET_NULL',
'on_update' => 'CASCADE',
],
[
'constraint' => 'fk_payment_staff',
'index_list' => ['staff_id'],
'ref_db_name' => null,
'ref_table_name' => 'staff',
'ref_index_list' => ['staff_id'],
'on_update' => 'CASCADE',
],
],
],
[
'CREATE TABLE `actor` (
`actor_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
`first_name` varchar(45) NOT NULL,
`last_name` varchar(45) NOT NULL,
`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`actor_id`),
KEY `idx_actor_last_name` (`last_name`)
) ENGINE=InnoDB AUTO_INCREMENT=201 DEFAULT CHARSET=utf8',
[],
],
[
'CREATE TABLE `address` (
`address_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
`address` varchar(50) NOT NULL,
`address2` varchar(50) DEFAULT NULL,
`district` varchar(20) NOT NULL,
`city_id` smallint(5) unsigned NOT NULL,
`postal_code` varchar(10) DEFAULT NULL,
`phone` varchar(20) NOT NULL,
`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`address_id`),
KEY `idx_fk_city_id` (`city_id`),
CONSTRAINT `fk_address_city` FOREIGN KEY (`city_id`) REFERENCES `city` (`city_id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=606 DEFAULT CHARSET=utf8',
[
[
'constraint' => 'fk_address_city',
'index_list' => ['city_id'],
'ref_db_name' => null,
'ref_table_name' => 'city',
'ref_index_list' => ['city_id'],
'on_update' => 'CASCADE',
],
],
],
];
}
/**
* @param array<string, array<string, bool|string>> $expected
* @psalm-param array<string, array{
* type: string,
* timestamp_not_null: bool,
* default_value?: string,
* default_current_timestamp?: bool,
* on_update_current_timestamp?: bool,
* expr?: string
* }> $expected
*
* @dataProvider getFieldsProvider
*/
public function testGetFields(string $query, array $expected): void
{
$parser = new Parser($query);
$this->assertEquals($expected, Table::getFields($parser->statements[0]));
}
/**
* @return array<int, array<int, string|array<string, array<string, bool|string>>>>
* @psalm-return list<array{string, array<string, array{
* type: string,
* timestamp_not_null: bool,
* default_value?: string,
* default_current_timestamp?: bool,
* on_update_current_timestamp?: bool,
* expr?: string
* }>}>
*/
public static function getFieldsProvider(): array
{
return [
[
'CREATE USER test',
[],
],
[
'CREATE TABLE `address` (
`address_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
`address` varchar(50) NOT NULL,
`address2` varchar(50) DEFAULT NULL,
`district` varchar(20) NOT NULL,
`city_id` smallint(5) unsigned NOT NULL,
`postal_code` varchar(10) DEFAULT NULL,
`phone` varchar(20) NOT NULL,
`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`address_id`),
KEY `idx_fk_city_id` (`city_id`),
CONSTRAINT `fk_address_city` FOREIGN KEY (`city_id`) REFERENCES `city` (`city_id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=606 DEFAULT CHARSET=utf8',
[
'address_id' => [
'type' => 'SMALLINT',
'timestamp_not_null' => false,
],
'address' => [
'type' => 'VARCHAR',
'timestamp_not_null' => false,
],
'address2' => [
'type' => 'VARCHAR',
'timestamp_not_null' => false,
'default_value' => 'NULL',
],
'district' => [
'type' => 'VARCHAR',
'timestamp_not_null' => false,
],
'city_id' => [
'type' => 'SMALLINT',
'timestamp_not_null' => false,
],
'postal_code' => [
'type' => 'VARCHAR',
'timestamp_not_null' => false,
'default_value' => 'NULL',
],
'phone' => [
'type' => 'VARCHAR',
'timestamp_not_null' => false,
],
'last_update' => [
'type' => 'TIMESTAMP',
'timestamp_not_null' => true,
'default_value' => 'CURRENT_TIMESTAMP',
'default_current_timestamp' => true,
'on_update_current_timestamp' => true,
],
],
],
[
'CREATE TABLE table1 (
a INT NOT NULL,
b VARCHAR(32),
c INT AS (a mod 10) VIRTUAL,
d VARCHAR(5) AS (left(b,5)) PERSISTENT
)',
[
'a' => [
'type' => 'INT',
'timestamp_not_null' => false,
],
'b' => [
'type' => 'VARCHAR',
'timestamp_not_null' => false,
],
'c' => [
'type' => 'INT',
'timestamp_not_null' => false,
'generated' => true,
'expr' => '(a mod 10)',
],
'd' => [
'type' => 'VARCHAR',
'timestamp_not_null' => false,
'generated' => true,
'expr' => '(left(b,5))',
],
],
],
];
}
}
@@ -0,0 +1,122 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser\Tests\Utils;
use PhpMyAdmin\SqlParser\Tests\TestCase;
use PhpMyAdmin\SqlParser\Token;
use PhpMyAdmin\SqlParser\Utils\Tokens;
class TokensTest extends TestCase
{
/**
* @param array<string, string>[] $find
* @param Token[] $replace
*
* @dataProvider replaceTokensProvider
*/
public function testReplaceTokens(string $list, array $find, array $replace, string $expected): void
{
$this->assertEquals($expected, Tokens::replaceTokens($list, $find, $replace));
}
/**
* @return array<int, array<int, string|array<string, string>[]|Token[]>>
* @psalm-return list<array{string, list<array<string, string>>, Token[], string}>
*/
public static function replaceTokensProvider(): array
{
return [
[
'SELECT * FROM /*x*/a/*c*/.b',
[
['value_str' => 'a'],
['token' => '.'],
],
[
new Token('c'),
new Token('.'),
],
'SELECT * FROM /*x*/c.b',
],
];
}
/**
* @param array<string, int|string> $pattern
*
* @dataProvider matchProvider
*/
public function testMatch(Token $token, array $pattern, bool $expected): void
{
$this->assertSame($expected, Tokens::match($token, $pattern));
}
/**
* @return array<int, array<int, Token|bool|array<string, int|string>>>
* @psalm-return list<array{Token, array<string, (int|string)>, bool}>
*/
public static function matchProvider(): array
{
return [
[
new Token(''),
[],
true,
],
[
new Token('"abc"', Token::TYPE_STRING, Token::FLAG_STRING_DOUBLE_QUOTES),
['token' => '"abc"'],
true,
],
[
new Token('"abc"', Token::TYPE_STRING, Token::FLAG_STRING_DOUBLE_QUOTES),
['value' => 'abc'],
true,
],
[
new Token('"abc"', Token::TYPE_STRING, Token::FLAG_STRING_DOUBLE_QUOTES),
['value_str' => 'ABC'],
true,
],
[
new Token('"abc"', Token::TYPE_STRING, Token::FLAG_STRING_DOUBLE_QUOTES),
['type' => Token::TYPE_STRING],
true,
],
[
new Token('"abc"', Token::TYPE_STRING, Token::FLAG_STRING_DOUBLE_QUOTES),
['flags' => Token::FLAG_STRING_DOUBLE_QUOTES],
true,
],
[
new Token('"abc"', Token::TYPE_STRING, Token::FLAG_STRING_DOUBLE_QUOTES),
['token' => '"abcd"'],
false,
],
[
new Token('"abc"', Token::TYPE_STRING, Token::FLAG_STRING_DOUBLE_QUOTES),
['value' => 'abcd'],
false,
],
[
new Token('"abc"', Token::TYPE_STRING, Token::FLAG_STRING_DOUBLE_QUOTES),
['value_str' => 'ABCd'],
false,
],
[
new Token('"abc"', Token::TYPE_STRING, Token::FLAG_STRING_DOUBLE_QUOTES),
['type' => Token::TYPE_NUMBER],
false,
],
[
new Token('"abc"', Token::TYPE_STRING, Token::FLAG_STRING_DOUBLE_QUOTES),
['flags' => Token::FLAG_STRING_SINGLE_QUOTES],
false,
],
];
}
}
@@ -0,0 +1 @@
ALTER..2
@@ -0,0 +1,94 @@
{
"query": "ALTER..2",
"lexer": {
"@type": "PhpMyAdmin\\SqlParser\\Lexer",
"str": "ALTER..2",
"len": 8,
"last": 8,
"list": {
"@type": "PhpMyAdmin\\SqlParser\\TokensList",
"tokens": [
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "ALTER",
"value": "ALTER",
"keyword": "ALTER",
"type": 1,
"flags": 3,
"position": 0
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": ".",
"value": ".",
"keyword": null,
"type": 2,
"flags": 16,
"position": 5
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": ".2",
"value": 0.2,
"keyword": null,
"type": 6,
"flags": 2,
"position": 6
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": null,
"value": null,
"keyword": null,
"type": 9,
"flags": 0,
"position": null
}
],
"count": 4,
"idx": 4
},
"delimiter": ";",
"delimiterLen": 1,
"strict": false,
"errors": []
},
"parser": {
"@type": "PhpMyAdmin\\SqlParser\\Parser",
"list": {
"@type": "@1"
},
"statements": [
{
"@type": "PhpMyAdmin\\SqlParser\\Statements\\AlterStatement",
"table": null,
"altered": [],
"options": null,
"first": 0,
"last": 0
}
],
"brackets": 0,
"strict": false,
"errors": []
},
"errors": {
"lexer": [],
"parser": [
[
"Unrecognized alter operation.",
{
"@type": "@2"
},
0
],
[
"Unexpected beginning of statement.",
{
"@type": "@4"
},
0
]
]
}
}
@@ -0,0 +1 @@
WITH](
@@ -0,0 +1,111 @@
{
"query": "WITH](",
"lexer": {
"@type": "PhpMyAdmin\\SqlParser\\Lexer",
"str": "WITH](",
"len": 6,
"last": 6,
"list": {
"@type": "PhpMyAdmin\\SqlParser\\TokensList",
"tokens": [
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "WITH",
"value": "WITH",
"keyword": "WITH",
"type": 1,
"flags": 3,
"position": 0
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "]",
"value": "]",
"keyword": null,
"type": 0,
"flags": 0,
"position": 4
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "(",
"value": "(",
"keyword": null,
"type": 2,
"flags": 16,
"position": 5
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": null,
"value": null,
"keyword": null,
"type": 9,
"flags": 0,
"position": null
}
],
"count": 4,
"idx": 4
},
"delimiter": ";",
"delimiterLen": 1,
"strict": false,
"errors": []
},
"parser": {
"@type": "PhpMyAdmin\\SqlParser\\Parser",
"list": {
"@type": "@1"
},
"statements": [
{
"@type": "PhpMyAdmin\\SqlParser\\Statements\\WithStatement",
"withers": [],
"cteStatementParser": null,
"options": {
"@type": "PhpMyAdmin\\SqlParser\\Components\\OptionsArray",
"options": []
},
"first": 0,
"last": 0
}
],
"brackets": 1,
"strict": false,
"errors": []
},
"errors": {
"lexer": [
[
"Unexpected character.",
"]",
4,
0
]
],
"parser": [
[
"The name of the CTE was expected.",
{
"@type": "@3"
},
0
],
[
"Unexpected end of the WITH CTE.",
{
"@type": "@3"
},
0
],
[
"Unexpected beginning of statement.",
{
"@type": "@3"
},
0
]
]
}
}
@@ -0,0 +1 @@
WITH*/A(
@@ -0,0 +1,113 @@
{
"query": "WITH*/A(",
"lexer": {
"@type": "PhpMyAdmin\\SqlParser\\Lexer",
"str": "WITH*/A(",
"len": 8,
"last": 8,
"list": {
"@type": "PhpMyAdmin\\SqlParser\\TokensList",
"tokens": [
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "WITH",
"value": "WITH",
"keyword": "WITH",
"type": 1,
"flags": 3,
"position": 0
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "*/",
"value": "*/",
"keyword": null,
"type": 4,
"flags": 2,
"position": 4
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "A",
"value": "A",
"keyword": null,
"type": 0,
"flags": 0,
"position": 6
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "(",
"value": "(",
"keyword": null,
"type": 2,
"flags": 16,
"position": 7
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": null,
"value": null,
"keyword": null,
"type": 9,
"flags": 0,
"position": null
}
],
"count": 5,
"idx": 5
},
"delimiter": ";",
"delimiterLen": 1,
"strict": false,
"errors": []
},
"parser": {
"@type": "PhpMyAdmin\\SqlParser\\Parser",
"list": {
"@type": "@1"
},
"statements": [
{
"@type": "PhpMyAdmin\\SqlParser\\Statements\\WithStatement",
"withers": {
"A": {
"@type": "PhpMyAdmin\\SqlParser\\Components\\WithKeyword",
"name": "A",
"columns": [],
"statement": null
}
},
"cteStatementParser": null,
"options": {
"@type": "PhpMyAdmin\\SqlParser\\Components\\OptionsArray",
"options": []
},
"first": 0,
"last": 3
}
],
"brackets": 0,
"strict": false,
"errors": []
},
"errors": {
"lexer": [],
"parser": [
[
"A closing bracket was expected.",
{
"@type": "@6"
},
0
],
[
"Unexpected end of the WITH CTE.",
{
"@type": "@6"
},
0
]
]
}
}
@@ -0,0 +1 @@
ALTeR=SET
@@ -0,0 +1,98 @@
{
"query": "ALTeR=SET",
"lexer": {
"@type": "PhpMyAdmin\\SqlParser\\Lexer",
"str": "ALTeR=SET",
"len": 9,
"last": 9,
"list": {
"@type": "PhpMyAdmin\\SqlParser\\TokensList",
"tokens": [
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "ALTeR",
"value": "ALTER",
"keyword": "ALTER",
"type": 1,
"flags": 3,
"position": 0
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "=",
"value": "=",
"keyword": null,
"type": 2,
"flags": 2,
"position": 5
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "SET",
"value": "SET",
"keyword": "SET",
"type": 1,
"flags": 11,
"position": 6
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": null,
"value": null,
"keyword": null,
"type": 9,
"flags": 0,
"position": null
}
],
"count": 4,
"idx": 4
},
"delimiter": ";",
"delimiterLen": 1,
"strict": false,
"errors": []
},
"parser": {
"@type": "PhpMyAdmin\\SqlParser\\Parser",
"list": {
"@type": "@1"
},
"statements": [
{
"@type": "PhpMyAdmin\\SqlParser\\Statements\\AlterStatement",
"table": null,
"altered": [],
"options": null,
"first": 0,
"last": 0
},
{
"@type": "PhpMyAdmin\\SqlParser\\Statements\\SetStatement",
"options": {
"@type": "PhpMyAdmin\\SqlParser\\Components\\OptionsArray",
"options": []
},
"end_options": null,
"set": [],
"first": 1,
"last": 2
}
],
"brackets": 0,
"strict": false,
"errors": []
},
"errors": {
"lexer": [],
"parser": [
[
"Unrecognized alter operation.",
{
"@type": "@2"
},
0
]
]
}
}
@@ -0,0 +1 @@
+0xO
@@ -0,0 +1,76 @@
{
"query": "+0xO",
"lexer": {
"@type": "PhpMyAdmin\\SqlParser\\Lexer",
"str": "+0xO",
"len": 4,
"last": 4,
"list": {
"@type": "PhpMyAdmin\\SqlParser\\TokensList",
"tokens": [
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "+0x",
"value": 0,
"keyword": null,
"type": 6,
"flags": 1,
"position": 0
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "O",
"value": "O",
"keyword": null,
"type": 0,
"flags": 0,
"position": 3
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": null,
"value": null,
"keyword": null,
"type": 9,
"flags": 0,
"position": null
}
],
"count": 3,
"idx": 3
},
"delimiter": ";",
"delimiterLen": 1,
"strict": false,
"errors": []
},
"parser": {
"@type": "PhpMyAdmin\\SqlParser\\Parser",
"list": {
"@type": "@1"
},
"statements": [],
"brackets": 0,
"strict": false,
"errors": []
},
"errors": {
"lexer": [],
"parser": [
[
"Unexpected beginning of statement.",
{
"@type": "@2"
},
0
],
[
"Unexpected beginning of statement.",
{
"@type": "@3"
},
0
]
]
}
}
@@ -0,0 +1 @@
-+0x!
@@ -0,0 +1,69 @@
{
"query": "-+0x!",
"lexer": {
"@type": "PhpMyAdmin\\SqlParser\\Lexer",
"str": "-+0x!",
"len": 5,
"last": 5,
"list": {
"@type": "PhpMyAdmin\\SqlParser\\TokensList",
"tokens": [
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "-+0x",
"value": 0,
"keyword": null,
"type": 6,
"flags": 9,
"position": 0
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "!",
"value": "!",
"keyword": null,
"type": 2,
"flags": 2,
"position": 4
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": null,
"value": null,
"keyword": null,
"type": 9,
"flags": 0,
"position": null
}
],
"count": 3,
"idx": 3
},
"delimiter": ";",
"delimiterLen": 1,
"strict": false,
"errors": []
},
"parser": {
"@type": "PhpMyAdmin\\SqlParser\\Parser",
"list": {
"@type": "@1"
},
"statements": [],
"brackets": 0,
"strict": false,
"errors": []
},
"errors": {
"lexer": [],
"parser": [
[
"Unexpected beginning of statement.",
{
"@type": "@2"
},
0
]
]
}
}
@@ -0,0 +1,3 @@
ALTER TABLE
`actor` MODIFY `actor_id` SMALLINT(5) UNSIGNED NOT NULL AUTO_INCREMENT,
AUTO_INCREMENT = 201;
@@ -0,0 +1,416 @@
{
"query": "ALTER TABLE\n `actor` MODIFY `actor_id` SMALLINT(5) UNSIGNED NOT NULL AUTO_INCREMENT,\n AUTO_INCREMENT = 201;",
"lexer": {
"@type": "PhpMyAdmin\\SqlParser\\Lexer",
"str": "ALTER TABLE\n `actor` MODIFY `actor_id` SMALLINT(5) UNSIGNED NOT NULL AUTO_INCREMENT,\n AUTO_INCREMENT = 201;",
"len": 113,
"last": 113,
"list": {
"@type": "PhpMyAdmin\\SqlParser\\TokensList",
"tokens": [
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "ALTER",
"value": "ALTER",
"keyword": "ALTER",
"type": 1,
"flags": 3,
"position": 0
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": " ",
"value": " ",
"keyword": null,
"type": 3,
"flags": 0,
"position": 5
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "TABLE",
"value": "TABLE",
"keyword": "TABLE",
"type": 1,
"flags": 3,
"position": 6
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "\n ",
"value": " ",
"keyword": null,
"type": 3,
"flags": 0,
"position": 11
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "`actor`",
"value": "actor",
"keyword": null,
"type": 8,
"flags": 2,
"position": 16
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": " ",
"value": " ",
"keyword": null,
"type": 3,
"flags": 0,
"position": 23
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "MODIFY",
"value": "MODIFY",
"keyword": "MODIFY",
"type": 1,
"flags": 1,
"position": 24
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": " ",
"value": " ",
"keyword": null,
"type": 3,
"flags": 0,
"position": 30
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "`actor_id`",
"value": "actor_id",
"keyword": null,
"type": 8,
"flags": 2,
"position": 31
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": " ",
"value": " ",
"keyword": null,
"type": 3,
"flags": 0,
"position": 41
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "SMALLINT",
"value": "SMALLINT",
"keyword": "SMALLINT",
"type": 1,
"flags": 11,
"position": 42
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "(",
"value": "(",
"keyword": null,
"type": 2,
"flags": 16,
"position": 50
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "5",
"value": 5,
"keyword": null,
"type": 6,
"flags": 0,
"position": 51
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": ")",
"value": ")",
"keyword": null,
"type": 2,
"flags": 16,
"position": 52
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": " ",
"value": " ",
"keyword": null,
"type": 3,
"flags": 0,
"position": 53
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "UNSIGNED",
"value": "UNSIGNED",
"keyword": "UNSIGNED",
"type": 1,
"flags": 3,
"position": 54
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": " ",
"value": " ",
"keyword": null,
"type": 3,
"flags": 0,
"position": 62
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "NOT NULL",
"value": "NOT NULL",
"keyword": "NOT NULL",
"type": 1,
"flags": 7,
"position": 63
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": " ",
"value": " ",
"keyword": null,
"type": 3,
"flags": 0,
"position": 71
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "AUTO_INCREMENT",
"value": "AUTO_INCREMENT",
"keyword": "AUTO_INCREMENT",
"type": 1,
"flags": 1,
"position": 72
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": ",",
"value": ",",
"keyword": null,
"type": 2,
"flags": 16,
"position": 86
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "\n ",
"value": " ",
"keyword": null,
"type": 3,
"flags": 0,
"position": 87
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "AUTO_INCREMENT",
"value": "AUTO_INCREMENT",
"keyword": "AUTO_INCREMENT",
"type": 1,
"flags": 1,
"position": 92
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": " ",
"value": " ",
"keyword": null,
"type": 3,
"flags": 0,
"position": 106
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "=",
"value": "=",
"keyword": null,
"type": 2,
"flags": 2,
"position": 107
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": " ",
"value": " ",
"keyword": null,
"type": 3,
"flags": 0,
"position": 108
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "201",
"value": 201,
"keyword": null,
"type": 6,
"flags": 0,
"position": 109
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": ";",
"value": ";",
"keyword": null,
"type": 9,
"flags": 0,
"position": 112
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": null,
"value": null,
"keyword": null,
"type": 9,
"flags": 0,
"position": null
}
],
"count": 29,
"idx": 29
},
"delimiter": ";",
"delimiterLen": 1,
"strict": false,
"errors": []
},
"parser": {
"@type": "PhpMyAdmin\\SqlParser\\Parser",
"list": {
"@type": "@1"
},
"statements": [
{
"@type": "PhpMyAdmin\\SqlParser\\Statements\\AlterStatement",
"table": {
"@type": "PhpMyAdmin\\SqlParser\\Components\\Expression",
"database": null,
"table": "actor",
"column": null,
"expr": "`actor`",
"alias": null,
"function": null,
"subquery": null
},
"altered": [
{
"@type": "PhpMyAdmin\\SqlParser\\Components\\AlterOperation",
"ROUTINE_OPTIONS": {
"COMMENT": [
1,
"var"
],
"LANGUAGE SQL": 2,
"CONTAINS SQL": 3,
"NO SQL": 3,
"READS SQL DATA": 3,
"MODIFIES SQL DATA": 3,
"SQL SECURITY": 4,
"DEFINER": 5,
"INVOKER": 5
},
"options": {
"@type": "PhpMyAdmin\\SqlParser\\Components\\OptionsArray",
"options": {
"1": "MODIFY"
}
},
"field": {
"@type": "PhpMyAdmin\\SqlParser\\Components\\Expression",
"database": null,
"table": null,
"column": "actor_id",
"expr": "`actor_id`",
"alias": null,
"function": null,
"subquery": null
},
"partitions": null,
"unknown": [
{
"@type": "@12"
},
{
"@type": "@13"
},
{
"@type": "@14"
},
{
"@type": "@15"
},
{
"@type": "@16"
},
{
"@type": "@17"
},
{
"@type": "@18"
},
{
"@type": "@19"
},
{
"@type": "@20"
},
{
"@type": "@21"
}
]
},
{
"@type": "PhpMyAdmin\\SqlParser\\Components\\AlterOperation",
"ROUTINE_OPTIONS": {
"COMMENT": [
1,
"var"
],
"LANGUAGE SQL": 2,
"CONTAINS SQL": 3,
"NO SQL": 3,
"READS SQL DATA": 3,
"MODIFIES SQL DATA": 3,
"SQL SECURITY": 4,
"DEFINER": 5,
"INVOKER": 5
},
"options": {
"@type": "PhpMyAdmin\\SqlParser\\Components\\OptionsArray",
"options": {
"1": {
"name": "AUTO_INCREMENT",
"equals": true,
"expr": "201",
"value": "201"
}
}
},
"field": null,
"partitions": null,
"unknown": []
}
],
"options": {
"@type": "PhpMyAdmin\\SqlParser\\Components\\OptionsArray",
"options": {
"3": "TABLE"
}
},
"first": 0,
"last": 27
}
],
"brackets": 0,
"strict": false,
"errors": []
},
"errors": {
"lexer": [],
"parser": []
}
}
@@ -0,0 +1,11 @@
CREATE TABLE `jos_core_acl_aro` (
`id` int(11) NOT NULL,
`section_value` varchar(240) NOT NULL DEFAULT '0',
`value` varchar(240) NOT NULL DEFAULT '',
`order_value` int(11) NOT NULL DEFAULT '0',
`name` varchar(255) NOT NULL DEFAULT '',
`hidden` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `jos_section_value_value_aro` (`section_value`(100),`value`(15)) USING BTREE,
KEY `jos_gacl_hidden_aro` (`hidden`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
File diff suppressed because it is too large Load Diff
@@ -0,0 +1 @@
update tbl_customer t set t.`description`=:yp where t.id=1;
@@ -0,0 +1,284 @@
{
"query": "update tbl_customer t set t.`description`=:yp where t.id=1;",
"lexer": {
"@type": "PhpMyAdmin\\SqlParser\\Lexer",
"str": "update tbl_customer t set t.`description`=:yp where t.id=1;",
"len": 59,
"last": 59,
"list": {
"@type": "PhpMyAdmin\\SqlParser\\TokensList",
"tokens": [
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "update",
"value": "UPDATE",
"keyword": "UPDATE",
"type": 1,
"flags": 3,
"position": 0
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": " ",
"value": " ",
"keyword": null,
"type": 3,
"flags": 0,
"position": 6
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "tbl_customer",
"value": "tbl_customer",
"keyword": null,
"type": 0,
"flags": 0,
"position": 7
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": " ",
"value": " ",
"keyword": null,
"type": 3,
"flags": 0,
"position": 19
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "t",
"value": "t",
"keyword": null,
"type": 0,
"flags": 0,
"position": 20
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": " ",
"value": " ",
"keyword": null,
"type": 3,
"flags": 0,
"position": 21
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "set",
"value": "SET",
"keyword": "SET",
"type": 1,
"flags": 11,
"position": 22
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": " ",
"value": " ",
"keyword": null,
"type": 3,
"flags": 0,
"position": 25
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "t",
"value": "t",
"keyword": null,
"type": 0,
"flags": 0,
"position": 26
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": ".",
"value": ".",
"keyword": null,
"type": 2,
"flags": 16,
"position": 27
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "`description`",
"value": "description",
"keyword": null,
"type": 8,
"flags": 2,
"position": 28
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "=",
"value": "=",
"keyword": null,
"type": 2,
"flags": 2,
"position": 41
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": ":yp",
"value": "yp",
"keyword": null,
"type": 8,
"flags": 16,
"position": 42
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": " ",
"value": " ",
"keyword": null,
"type": 3,
"flags": 0,
"position": 45
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "where",
"value": "WHERE",
"keyword": "WHERE",
"type": 1,
"flags": 3,
"position": 46
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": " ",
"value": " ",
"keyword": null,
"type": 3,
"flags": 0,
"position": 51
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "t",
"value": "t",
"keyword": null,
"type": 0,
"flags": 0,
"position": 52
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": ".",
"value": ".",
"keyword": null,
"type": 2,
"flags": 16,
"position": 53
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "id",
"value": "id",
"keyword": null,
"type": 0,
"flags": 0,
"position": 54
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "=",
"value": "=",
"keyword": null,
"type": 2,
"flags": 2,
"position": 56
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": "1",
"value": 1,
"keyword": null,
"type": 6,
"flags": 0,
"position": 57
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": ";",
"value": ";",
"keyword": null,
"type": 9,
"flags": 0,
"position": 58
},
{
"@type": "PhpMyAdmin\\SqlParser\\Token",
"token": null,
"value": null,
"keyword": null,
"type": 9,
"flags": 0,
"position": null
}
],
"count": 23,
"idx": 23
},
"delimiter": ";",
"delimiterLen": 1,
"strict": false,
"errors": []
},
"parser": {
"@type": "PhpMyAdmin\\SqlParser\\Parser",
"list": {
"@type": "@1"
},
"statements": [
{
"@type": "PhpMyAdmin\\SqlParser\\Statements\\UpdateStatement",
"tables": [
{
"@type": "PhpMyAdmin\\SqlParser\\Components\\Expression",
"database": null,
"table": "tbl_customer",
"column": null,
"expr": "tbl_customer",
"alias": "t",
"function": null,
"subquery": null
}
],
"set": [
{
"@type": "PhpMyAdmin\\SqlParser\\Components\\SetOperation",
"column": "t.`description`",
"value": ":yp"
}
],
"where": [
{
"@type": "PhpMyAdmin\\SqlParser\\Components\\Condition",
"identifiers": [
"t",
"id"
],
"isOperator": false,
"expr": "t.id=1"
}
],
"order": null,
"limit": null,
"join": null,
"options": {
"@type": "PhpMyAdmin\\SqlParser\\Components\\OptionsArray",
"options": []
},
"first": 0,
"last": 20
}
],
"brackets": 0,
"strict": false,
"errors": []
},
"errors": {
"lexer": [],
"parser": []
}
}
@@ -0,0 +1 @@
ALTER TABLE `mail_template` CHANGE COLUMN `mtpl_group` `mtpl_group` ENUM('ORDER') NULL DEFAULT NULL ;

Some files were not shown because too many files have changed in this diff Show More