From 08ec3fde45a12b2310772052d2530d5a8c7ce421 Mon Sep 17 00:00:00 2001 From: "Fawzi E. Abdulfattah" Date: Mon, 6 Dec 2021 02:50:30 +0200 Subject: [PATCH] Fixing phpstan issues, and adding more test cases --- src/Statements/CreateStatement.php | 17 +++++++--- src/Statements/InsertStatement.php | 10 +++++- src/Statements/WithStatement.php | 35 +++++++++++++------- tests/Builder/CreateStatementTest.php | 14 ++++++++ tests/Parser/WithStatementTest.php | 10 +++--- tests/data/parser/parseWithStatement.out | 3 +- tests/data/parser/parseWithStatement2.out | 3 +- tests/data/parser/parseWithStatementErr.out | 3 +- tests/data/parser/parseWithStatementErr3.out | 2 +- tests/data/parser/parseWithStatementErr5.out | 21 ++++-------- tests/data/parser/parseWithStatementErr6.out | 6 ++-- 11 files changed, 82 insertions(+), 42 deletions(-) diff --git a/src/Statements/CreateStatement.php b/src/Statements/CreateStatement.php index db28dc501..0fde49f04 100644 --- a/src/Statements/CreateStatement.php +++ b/src/Statements/CreateStatement.php @@ -447,6 +447,13 @@ public function build() . Expression::build($this->like); } + if ($this->with !== null) { + return 'CREATE ' + . OptionsArray::build($this->options) . ' ' + . Expression::build($this->name) . ' ' + . $this->with->build(); + } + $partition = ''; if (! empty($this->partitionBy)) { @@ -477,9 +484,9 @@ public function build() . $partition; } elseif ($this->options->has('VIEW')) { $builtStatement = ''; - if ($this->select) { + if ($this->select !== null) { $builtStatement = $this->select->build(); - } elseif ($this->with) { + } elseif ($this->with !== null) { $builtStatement = $this->with->build(); } @@ -564,7 +571,7 @@ public function parse(Parser $parser, TokensList $list) if (($token->type === Token::TYPE_KEYWORD) && ($token->keyword === 'SELECT')) { /* CREATE TABLE ... SELECT */ $this->select = new SelectStatement($parser, $list); - } elseif (($token->type === Token::TYPE_KEYWORD) && ($token->keyword === 'WITH')) { + } elseif ($token->type === Token::TYPE_KEYWORD && ($token->keyword === 'WITH')) { /* CREATE TABLE WITH */ $this->with = new WithStatement($parser, $list); } elseif ( @@ -575,10 +582,10 @@ public function parse(Parser $parser, TokensList $list) /* CREATE TABLE ... AS SELECT */ $list->idx = $nextidx; $this->select = new SelectStatement($parser, $list); - } elseif ($list->tokens[$nextidx]->value === 'SELECT') { + } elseif ($list->tokens[$nextidx]->value === 'WITH') { /* CREATE TABLE WITH */ $list->idx = $nextidx; - $this->select = new WithStatement($parser, $list); + $this->with = new WithStatement($parser, $list); } } elseif ($token->type === Token::TYPE_KEYWORD && $token->keyword === 'LIKE') { /* CREATE TABLE `new_tbl` LIKE 'orig_tbl' */ diff --git a/src/Statements/InsertStatement.php b/src/Statements/InsertStatement.php index 6ecc982f8..d21662a83 100644 --- a/src/Statements/InsertStatement.php +++ b/src/Statements/InsertStatement.php @@ -98,6 +98,14 @@ class InsertStatement extends Statement */ public $select; + /** + * If WITH CTE is present + * holds the WithStatement. + * + * @var WithStatement|null + */ + public $with; + /** * If ON DUPLICATE KEY UPDATE clause is present * holds the SetOperation. @@ -211,7 +219,7 @@ public function parse(Parser $parser, TokensList $list) } elseif ($token->keyword === 'SELECT') { $this->select = new SelectStatement($parser, $list); } elseif ($token->keyword === 'WITH') { - $this->select = new WithStatement($parser, $list); + $this->with = new WithStatement($parser, $list); } else { $parser->error('Unexpected keyword.', $token); break; diff --git a/src/Statements/WithStatement.php b/src/Statements/WithStatement.php index 786389140..d9163d16c 100644 --- a/src/Statements/WithStatement.php +++ b/src/Statements/WithStatement.php @@ -108,13 +108,14 @@ public function parse(Parser $parser, TokensList $list) } if ($state === 0) { - if ($token->type === Token::TYPE_NONE) { - $wither = $token->value; - $this->withers[$wither] = new WithKeyword($wither); - $state = 1; - } else { + if ($token->type !== Token::TYPE_NONE) { $parser->error('The name of the CTE was expected.', $token); + break; } + + $wither = $token->value; + $this->withers[$wither] = new WithKeyword($wither); + $state = 1; } elseif ($state === 1) { if ($token->type === Token::TYPE_OPERATOR && $token->value === '(') { $this->withers[$wither]->columns = Array2d::parse($parser, $list); @@ -133,21 +134,29 @@ public function parse(Parser $parser, TokensList $list) $state = 3; } elseif ($state === 3) { - if ($token->value !== '(') { + $idxBeforeGetNext = $list->idx; + + // We want to get the next non-comment and non-space token after $token + // therefore, the first getNext call will start with the current $idx which's $token, + // will return it and increase $idx by 1, which's not guaranteed to be non-comment + // and non-space, that's why we're calling getNext again. + $list->getNext(); + $nextKeyword = $list->getNext(); + + if (! ($token->value === '(' && $nextKeyword->value === 'SELECT')) { $parser->error('Subquery of the CTE was expected.', $token); + $list->idx = $idxBeforeGetNext; break; } - if ($token->value !== 'SELECT') { - $parser->error('Unexpected keyword', $token); - break; - } + // Restore the index + $list->idx = $idxBeforeGetNext; ++$list->idx; $subList = $this->getSubTokenList($list); if ($subList instanceof ParserException) { $parser->errors[] = $subList; - continue; + break; } $subParser = new Parser($subList); @@ -156,6 +165,8 @@ public function parse(Parser $parser, TokensList $list) foreach ($subParser->errors as $error) { $parser->errors[] = $error; } + + break; } $this->withers[$wither]->statement = $subParser; @@ -229,7 +240,7 @@ public function parse(Parser $parser, TokensList $list) // 5 is the only valid end state if ($state !== 5) { - $parser->error('Unexpected end of WITH CTE.', $token); + $parser->error('Unexpected end of WITH CTE.', $list->tokens[$list->idx]); } --$list->idx; diff --git a/tests/Builder/CreateStatementTest.php b/tests/Builder/CreateStatementTest.php index 0e8ac655c..d235bfb31 100644 --- a/tests/Builder/CreateStatementTest.php +++ b/tests/Builder/CreateStatementTest.php @@ -164,6 +164,20 @@ public function testBuilderTable(): void ') ENGINE=InnoDB DEFAULT CHARSET=latin1'; $parser = new Parser($query); $this->assertEquals($query, $parser->statements[0]->build()); + + /* Assertion 5 */ + $parser = new Parser( + 'CREATE table table_name WITH' . + ' cte (col1) AS ( SELECT 1 UNION ALL SELECT 2 )' . + ' SELECT col1 FROM cte' + ); + $stmt = $parser->statements[0]; + + $this->assertEquals( + 'CREATE TABLE table_name WITH' . + ' cte(col1) AS (SELECT 1 UNION ALL SELECT 2)', + $stmt->build() + ); } public function testBuilderPartitions(): void diff --git a/tests/Parser/WithStatementTest.php b/tests/Parser/WithStatementTest.php index f3f671fd9..95f88d299 100644 --- a/tests/Parser/WithStatementTest.php +++ b/tests/Parser/WithStatementTest.php @@ -13,15 +13,16 @@ class WithStatementTest extends TestCase { /** - * @param mixed $test - * * @dataProvider parseWith */ - public function testParse($test): void + public function testParse(string $test): void { $this->runParserTest($test); } + /** + * @return array> + */ public function parseWith(): array { return [ @@ -35,6 +36,7 @@ public function parseWith(): array ['parser/parseWithStatementErr3'], ['parser/parseWithStatementErr4'], ['parser/parseWithStatementErr5'], + ['parser/parseWithStatementErr6'], ]; } @@ -81,7 +83,7 @@ public function testWithHasErrors(): void $this->assertCount(0, $lexerErrors); $parser = new Parser($lexer->list); $parserErrors = $this->getErrorsAsArray($parser); - $this->assertCount(2, $parserErrors); + $this->assertCount(4, $parserErrors); } public function testWithEmbedParenthesis(): void diff --git a/tests/data/parser/parseWithStatement.out b/tests/data/parser/parseWithStatement.out index fa660cde2..9fa3073b6 100644 --- a/tests/data/parser/parseWithStatement.out +++ b/tests/data/parser/parseWithStatement.out @@ -743,7 +743,8 @@ }, "values": null, "set": null, - "select": { + "select": null, + "with": { "@type": "PhpMyAdmin\\SqlParser\\Statements\\WithStatement", "OPTIONS": { "RECURSIVE": 1 diff --git a/tests/data/parser/parseWithStatement2.out b/tests/data/parser/parseWithStatement2.out index 245232fb9..677aa58c5 100644 --- a/tests/data/parser/parseWithStatement2.out +++ b/tests/data/parser/parseWithStatement2.out @@ -815,7 +815,8 @@ }, "values": null, "set": null, - "select": { + "select": null, + "with": { "@type": "PhpMyAdmin\\SqlParser\\Statements\\WithStatement", "OPTIONS": { "RECURSIVE": 1 diff --git a/tests/data/parser/parseWithStatementErr.out b/tests/data/parser/parseWithStatementErr.out index 3990aa10c..13834b775 100644 --- a/tests/data/parser/parseWithStatementErr.out +++ b/tests/data/parser/parseWithStatementErr.out @@ -734,7 +734,8 @@ }, "values": null, "set": null, - "select": { + "select": null, + "with": { "@type": "PhpMyAdmin\\SqlParser\\Statements\\WithStatement", "OPTIONS": { "RECURSIVE": 1 diff --git a/tests/data/parser/parseWithStatementErr3.out b/tests/data/parser/parseWithStatementErr3.out index 4aebe7041..3c43475ca 100644 --- a/tests/data/parser/parseWithStatementErr3.out +++ b/tests/data/parser/parseWithStatementErr3.out @@ -1654,7 +1654,7 @@ } }, "first": 0, - "last": 30 + "last": 29 } ], "brackets": 0, diff --git a/tests/data/parser/parseWithStatementErr5.out b/tests/data/parser/parseWithStatementErr5.out index 5c35128b9..b00c29766 100644 --- a/tests/data/parser/parseWithStatementErr5.out +++ b/tests/data/parser/parseWithStatementErr5.out @@ -862,14 +862,7 @@ 2 ] }, - "withers": { - "col1": { - "@type": "PhpMyAdmin\\SqlParser\\Components\\WithKeyword", - "name": "col1", - "columns": [], - "statement": null - } - }, + "withers": [], "END_OPTIONS": [], "options": { "@type": "PhpMyAdmin\\SqlParser\\Components\\OptionsArray", @@ -898,10 +891,10 @@ } }, "first": 0, - "last": 9 + "last": 7 } ], - "brackets": 0, + "brackets": 1, "strict": false, "errors": [] }, @@ -916,16 +909,16 @@ 0 ], [ - "Unexpected token.", + "Unexpected end of WITH CTE.", { - "@type": "@12" + "@type": "@10" }, 0 ], [ - "Unexpected end of WITH CTE.", + "Unexpected beginning of statement.", { - "@type": "@12" + "@type": "@11" }, 0 ], diff --git a/tests/data/parser/parseWithStatementErr6.out b/tests/data/parser/parseWithStatementErr6.out index 1a58b2358..652168b42 100644 --- a/tests/data/parser/parseWithStatementErr6.out +++ b/tests/data/parser/parseWithStatementErr6.out @@ -1031,7 +1031,8 @@ }, "values": null, "set": null, - "select": { + "select": null, + "with": { "@type": "PhpMyAdmin\\SqlParser\\Statements\\WithStatement", "OPTIONS": { "RECURSIVE": 1 @@ -1173,6 +1174,7 @@ ], "set": null, "select": null, + "with": null, "onDuplicateSet": null, "CLAUSES": [], "END_OPTIONS": [], @@ -1412,7 +1414,7 @@ "lexer": [], "parser": [ [ - "Unexpected keyword", + "Subquery of the CTE was expected.", { "@type": "@18" },