mirror of
https://gitlab.com/signalytic/client-external/streamline/streamline-emr.git
synced 2026-09-12 11:11:31 +00:00
resolved conflicts
This commit is contained in:
+358
@@ -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/>' .
|
||||
' <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/>' .
|
||||
' <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]])
|
||||
);
|
||||
}
|
||||
}
|
||||
+613
@@ -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/>' .
|
||||
' <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/>' .
|
||||
' *<br/>' .
|
||||
'<span class="sql-reserved">FROM</span><br/>' .
|
||||
' tbl<br/>' .
|
||||
'<span class="sql-reserved">WHERE</span><br/>' .
|
||||
' <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/>' .
|
||||
' id,<br/>' .
|
||||
' <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/>' .
|
||||
' <span class="sql-variable">`tbl`</span><br/>' .
|
||||
'<span class="sql-reserved">WHERE</span><br/>' .
|
||||
' 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/>' .
|
||||
' id<br/>' .
|
||||
'<span class="sql-reserved">ORDER BY</span><br/>' .
|
||||
' 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/>' .
|
||||
' <span class="sql-comment">/* Comment */</span> *<br/>' .
|
||||
'<span class="sql-reserved">FROM</span><br/>' .
|
||||
' tbl <span class="sql-comment"># Comment</span><br/>' .
|
||||
'<span class="sql-reserved">WHERE</span><br/>' .
|
||||
' <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/>' .
|
||||
' *<br/>' .
|
||||
'<span class="sql-reserved">FROM</span><br/>' .
|
||||
' tbl<br/>' .
|
||||
'<span class="sql-reserved">WHERE</span><br/>' .
|
||||
' <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/>' .
|
||||
' <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/>' .
|
||||
' <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/>' .
|
||||
' <span class="sql-reserved">FROM</span><br/>' .
|
||||
' tbl<br/>' .
|
||||
' <span class="sql-reserved">SELECT</span><br/>' .
|
||||
' *;<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/>' .
|
||||
' <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/>' .
|
||||
' <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/>' .
|
||||
' <span class="sql-string">\'<s>xss\'</span><br/>' .
|
||||
'<span class="sql-reserved">FROM</span><br/>' .
|
||||
' <span class="sql-variable">`<s>xss`</span>,' .
|
||||
'<br/> < s > nxss <span class="sql-comment">/*s<s>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/>' .
|
||||
' <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/>' .
|
||||
' <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/>' .
|
||||
' <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/>' .
|
||||
' <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/>' .
|
||||
' <span class="sql-variable">`query`</span> ' .
|
||||
'<span class="sql-keyword">TEXT</span> <span class="sql-reserved">NOT NULL</span>,<br/>' .
|
||||
' <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/>' .
|
||||
' *<br/>' .
|
||||
'<span class="sql-reserved">FROM</span><br/>' .
|
||||
' tbl<br/>' .
|
||||
'<span class="sql-reserved">WHERE</span><br/>' .
|
||||
' 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/>' .
|
||||
' *<br/>' .
|
||||
'<span class="sql-reserved">FROM</span><br/>' .
|
||||
' tbl<br/>' .
|
||||
'<span class="sql-reserved">WHERE</span><br/>' .
|
||||
' 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,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user