diff --git a/CHANGELOG.md b/CHANGELOG.md index a2c744f5..6ca3fffa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,12 @@ - Add Translator::setLocale() method (#599) +# [5.10.3] - YYYY-MM-DD + +### Fixed + +- Add "RECURSIVE" on build() for "WITH RECURSIVE" on the WithStatement class (#605) + ## [5.10.2] - 2024-12-05 ### Added diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 99136248..23a0753d 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -716,22 +716,22 @@ parameters: path: src/Parsers/OptionsArrays.php - - message: "#^Property PhpMyAdmin\\\\SqlParser\\\\Components\\\\OptionsArray\\:\\:\\$options \\(array\\\\) does not accept non\\-empty\\-array\\\\.$#" + message: "#^Property PhpMyAdmin\\\\SqlParser\\\\Components\\\\OptionsArray\\:\\:\\$options \\(array\\\\) does not accept non\\-empty\\-array\\\\.$#" count: 2 path: src/Parsers/OptionsArrays.php - - message: "#^Property PhpMyAdmin\\\\SqlParser\\\\Components\\\\OptionsArray\\:\\:\\$options \\(array\\\\) does not accept non\\-empty\\-array\\\\.$#" + message: "#^Property PhpMyAdmin\\\\SqlParser\\\\Components\\\\OptionsArray\\:\\:\\$options \\(array\\\\) does not accept non\\-empty\\-array\\\\.$#" count: 1 path: src/Parsers/OptionsArrays.php - - message: "#^Property PhpMyAdmin\\\\SqlParser\\\\Components\\\\OptionsArray\\:\\:\\$options \\(array\\\\) does not accept non\\-empty\\-array\\\\.$#" + message: "#^Property PhpMyAdmin\\\\SqlParser\\\\Components\\\\OptionsArray\\:\\:\\$options \\(array\\\\) does not accept non\\-empty\\-array\\\\.$#" count: 3 path: src/Parsers/OptionsArrays.php - - message: "#^Property PhpMyAdmin\\\\SqlParser\\\\Components\\\\OptionsArray\\:\\:\\$options \\(array\\\\) does not accept non\\-empty\\-array\\\\.$#" + message: "#^Property PhpMyAdmin\\\\SqlParser\\\\Components\\\\OptionsArray\\:\\:\\$options \\(array\\\\) does not accept non\\-empty\\-array\\\\.$#" count: 3 path: src/Parsers/OptionsArrays.php @@ -1722,7 +1722,7 @@ parameters: - message: "#^Dynamic call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertEquals\\(\\)\\.$#" - count: 33 + count: 34 path: tests/Builder/CreateStatementTest.php - @@ -1977,7 +1977,7 @@ parameters: - message: "#^Dynamic call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertEquals\\(\\)\\.$#" - count: 8 + count: 9 path: tests/Components/OptionsArrayTest.php - @@ -2212,12 +2212,12 @@ parameters: - message: "#^Dynamic call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertCount\\(\\)\\.$#" - count: 8 + count: 14 path: tests/Parser/WithStatementTest.php - message: "#^Dynamic call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertEquals\\(\\)\\.$#" - count: 3 + count: 5 path: tests/Parser/WithStatementTest.php - diff --git a/src/Statements/WithStatement.php b/src/Statements/WithStatement.php index a0d4f92f..c17298f0 100644 --- a/src/Statements/WithStatement.php +++ b/src/Statements/WithStatement.php @@ -269,11 +269,17 @@ public function parse(Parser $parser, TokensList $list): void public function build(): string { - $str = 'WITH '; + $initial = true; + $str = 'WITH'; + + if ($this->options !== null && $this->options->options !== []) { + $str .= ' ' . $this->options->build(); + } foreach ($this->withers as $wither) { - $str .= $str === 'WITH ' ? '' : ', '; + $str .= $initial ? ' ' : ', '; $str .= $wither->build(); + $initial = false; } $str .= ' '; diff --git a/tests/Builder/CreateStatementTest.php b/tests/Builder/CreateStatementTest.php index e2b187ba..3ba1c1c5 100644 --- a/tests/Builder/CreateStatementTest.php +++ b/tests/Builder/CreateStatementTest.php @@ -19,6 +19,8 @@ use PhpMyAdmin\SqlParser\TokenType; use PHPUnit\Framework\Attributes\DataProvider; +use function implode; + class CreateStatementTest extends TestCase { public function testBuilder(): void @@ -401,6 +403,33 @@ public function testBuilderView(): void . ' AS (SELECT 1 UNION ALL SELECT 2) SELECT col1 FROM cte AS `d` ', $stmt->build(), ); + + $parser = new Parser( + implode("\n", [ + 'CREATE VIEW number_sequence_view AS', + 'WITH RECURSIVE number_sequence AS (', + ' SELECT 1 AS `number`', + ' UNION ALL', + ' SELECT `number` + 1', + ' FROM number_sequence', + ' WHERE `number` < 5', + ')', + 'SELECT * FROM number_sequence;', + ]), + ); + $stmt = $parser->statements[0]; + $this->assertEquals( + 'CREATE VIEW number_sequence_view AS' + . ' WITH RECURSIVE number_sequence AS (' + . 'SELECT 1 AS `number`' + . ' UNION ALL' + . ' SELECT `number`+ 1' + . ' FROM number_sequence' + . ' WHERE `number` < 5' + . ')' + . ' SELECT * FROM number_sequence ', + $stmt->build(), + ); } public function testBuilderViewComplex(): void diff --git a/tests/Components/OptionsArrayTest.php b/tests/Components/OptionsArrayTest.php index 1785f3ef..c0b71d8f 100644 --- a/tests/Components/OptionsArrayTest.php +++ b/tests/Components/OptionsArrayTest.php @@ -136,4 +136,17 @@ public function testBuild(): void $component->build(), ); } + + public function testBuildWithRecursive(): void + { + $component = OptionsArrays::parse( + new Parser(), + $this->getTokensList('RECURSIVE'), + ['RECURSIVE' => 1], + ); + $this->assertEquals( + 'RECURSIVE', + $component->build(), + ); + } } diff --git a/tests/Parser/WithStatementTest.php b/tests/Parser/WithStatementTest.php index 12f32ab7..a6c716c7 100644 --- a/tests/Parser/WithStatementTest.php +++ b/tests/Parser/WithStatementTest.php @@ -70,6 +70,70 @@ public function testWith(): void $this->assertEquals($expected, $parser->statements[0]->build()); } + public function testWithRecursive(): void + { + $sql = <<<'SQL' +WITH RECURSIVE number_sequence AS ( + SELECT 1 AS `number` + UNION ALL + SELECT `number` + 1 + FROM number_sequence + WHERE `number` < 5 +) +SELECT * FROM number_sequence; +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 RECURSIVE number_sequence AS (SELECT 1 AS `number` UNION ALL SELECT `number`+ 1 FROM number_sequence WHERE `number` < 5) SELECT * FROM number_sequence +SQL; + // phpcs:enable + $this->assertEquals($expected, $parser->statements[0]->build()); + } + + public function testWithRecursiveWithers(): void + { + $sql = <<<'SQL' +WITH RECURSIVE cte AS +( + SELECT 1 AS n, CAST('abc' AS CHAR(20)) AS str + UNION ALL + SELECT n + 1, CONCAT(str, str) FROM cte WHERE n < 3 +), cte2 AS +( + SELECT 1 AS n, CAST('def' AS CHAR(20)) AS str + UNION ALL + SELECT n + 1, CONCAT(str, str) FROM cte WHERE n < 3 +) +SELECT * FROM cte UNION SELECT * FROM cte2; +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 RECURSIVE cte AS (SELECT 1 AS `n`, CAST('abc' AS CHAR(20)) AS `str` UNION ALL SELECT n+ 1, CONCAT(str, str) FROM cte WHERE n < 3), cte2 AS (SELECT 1 AS `n`, CAST('def' AS CHAR(20)) AS `str` UNION ALL SELECT n+ 1, CONCAT(str, str) FROM cte WHERE n < 3) SELECT * FROM cte UNION SELECT * FROM cte2 +SQL; + // phpcs:enable + $this->assertEquals($expected, $parser->statements[0]->build()); + } + public function testWithHasErrors(): void { $sql = <<<'SQL'