Skip to content

Commit 30bc157

Browse files
committed
Fix bugs in CREATE TABLE parsing
1 parent a730897 commit 30bc157

24 files changed

Lines changed: 521 additions & 195 deletions

‎composer.json‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
"require": {
1616
"php": "^7.1|^8",
1717
"ext-pdo": "*",
18-
"phpmyadmin/sql-parser": "^5.4",
1918
"nyholm/dsn": "^2.0"
2019
},
2120
"autoload": {

‎phpunit.xml.dist‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
4+
bootstrap="vendor/autoload.php"
5+
backupGlobals="false"
6+
beStrictAboutCoversAnnotation="true"
7+
beStrictAboutOutputDuringTests="true"
8+
beStrictAboutTestsThatDoNotTestAnything="false"
9+
beStrictAboutTodoAnnotatedTests="true"
10+
colors="true"
11+
verbose="true"
12+
executionOrder="random"
13+
>
14+
<testsuites>
15+
<testsuite name="php-mysql-engine">
16+
<directory>tests</directory>
17+
</testsuite>
18+
</testsuites>
19+
</phpunit>

‎src/FakePdoStatement.php‎

Lines changed: 50 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class FakePdoStatement extends \PDOStatement
5151
private $realStatement = null;
5252

5353
/**
54-
* @var array<string, scalar>
54+
* @var array<string|int, scalar>
5555
*/
5656
private $boundValues = [];
5757

@@ -82,10 +82,10 @@ public function bindValue($key, $value, $type = \PDO::PARAM_STR) : void
8282

8383
/**
8484
* Overriding execute method to add query logging
85-
*
85+
* @param ?array $params
8686
* @return bool
8787
*/
88-
public function execute(?array $params = null)
88+
public function execute($params = null)
8989
{
9090
$sql = $this->getExecutedSql($this->boundValues ?: $params);
9191

@@ -96,55 +96,18 @@ public function execute(?array $params = null)
9696
}
9797
}
9898

99-
//echo "\n" . $sql . "\n";
100-
101-
if (stripos($sql, 'CREATE TABLE') !== false
102-
|| stripos($sql, 'DROP TABLE') !== false
103-
|| stripos($sql, 'SHOW TABLES') !== false
104-
) {
105-
$parsed_query = new \PhpMyAdmin\SqlParser\Parser($sql);
106-
107-
if ($parsed_query->errors) {
108-
throw $parsed_query->errors[0];
109-
}
110-
111-
if (!isset($parsed_query->statements[0])) {
112-
throw new \UnexpectedValueException('Bad query ' . $sql);
113-
}
114-
115-
$statement = $parsed_query->statements[0];
116-
117-
switch (get_class($statement)) {
118-
case \PhpMyAdmin\SqlParser\Statements\CreateStatement::class:
119-
Processor\CreateProcessor::process($this->conn, $statement);
120-
break;
121-
122-
case \PhpMyAdmin\SqlParser\Statements\DropStatement::class:
123-
$this->conn->getServer()->dropTable(
124-
$this->conn->databaseName,
125-
$statement->fields[0]->table
126-
);
127-
break;
128-
case \PhpMyAdmin\SqlParser\Statements\ShowStatement::class:
129-
if (count($statement->unknown) === 7
130-
&& $statement->unknown[4]->value === 'LIKE'
131-
) {
132-
if ($this->conn->getServer()->getTable(
133-
$this->conn->databaseName,
134-
$statement->unknown[4]->value
135-
)) {
136-
$this->result = [[$statement->unknown[4]->value]];
137-
}
99+
if (stripos($sql, 'CREATE TABLE') !== false) {
100+
$create_queries = (new Parser\CreateTableParser())->parse($sql);
138101

139-
$this->result = [];
140-
141-
return true;
142-
}
102+
foreach ($create_queries as $create_query) {
103+
Processor\CreateProcessor::process($this->conn, $create_query);
143104
}
144105

145106
return true;
146107
}
147108

109+
//echo "\n" . $sql . "\n";
110+
148111
$parsed_query = Parser\SQLParser::parse($sql);
149112

150113
$this->result = null;
@@ -210,6 +173,23 @@ function ($row) {
210173
);
211174
break;
212175

176+
case Query\DropTableQuery::class:
177+
$this->conn->getServer()->dropTable(
178+
$this->conn->databaseName,
179+
$parsed_query->table
180+
);
181+
break;
182+
case Query\ShowTablesQuery::class:
183+
if ($this->conn->getServer()->getTable(
184+
$this->conn->databaseName,
185+
$parsed_query->pattern
186+
)) {
187+
$this->result = [[$parsed_query->pattern]];
188+
} else {
189+
$this->result = [];
190+
}
191+
break;
192+
213193
default:
214194
throw new \UnexpectedValueException('Unsupported operation type ' . $sql);
215195
}
@@ -231,10 +211,15 @@ public function rowCount() : int
231211
return $this->affectedRows;
232212
}
233213

214+
/**
215+
* @param int $fetch_style
216+
* @param int $cursor_orientation
217+
* @param int $cursor_offset
218+
*/
234219
public function fetch(
235-
int $fetch_style = -123,
236-
int $cursor_orientation = \PDO::FETCH_ORI_NEXT,
237-
int $cursor_offset = 0
220+
$fetch_style = -123,
221+
$cursor_orientation = \PDO::FETCH_ORI_NEXT,
222+
$cursor_offset = 0
238223
) {
239224
if ($fetch_style === -123) {
240225
$fetch_style = $this->fetchMode;
@@ -283,17 +268,19 @@ public function fetch(
283268

284269
/**
285270
* @param int $fetch_style
286-
* @param mixed $args
271+
* @param string $fetch_argument
272+
* @param array $ctor_args
287273
*/
288-
public function fetchAll(int $fetch_style = -123, ...$args) : array
274+
public function fetchAll($fetch_style = -123, $fetch_argument = NULL, $ctor_args = NULL) : array
289275
{
290276
if ($fetch_style === -123) {
291277
$fetch_style = $this->fetchMode;
292278
$fetch_argument = $this->fetchArgument;
293279
$ctor_args = $this->fetchConstructorArgs;
294280
} else {
295-
$fetch_argument = $args[0] ?? null;
296-
$ctor_args = $args[1] ?? [];
281+
// may have to uncomment for PHP 8
282+
//$fetch_argument = $args[0] ?? null;
283+
//$ctor_args = $args[1] ?? [];
297284
}
298285

299286
if ($fetch_style === \PDO::FETCH_ASSOC) {
@@ -387,12 +374,15 @@ function ($row) use ($fetch_argument, $ctor_args) {
387374

388375
/**
389376
* @param int $fetch_style
390-
* @param mixed $args
377+
* @param mixed $fetch_argument
378+
* @param array $ctorargs
379+
* @param array ...$args
391380
*/
392-
public function setFetchMode(int $mode, ...$args) : bool
381+
public function setFetchMode($mode, $fetch_argument = null, $ctorargs = []) : bool
393382
{
394-
$fetch_argument = $args[0] ?? null;
395-
$ctorargs = $args[1] ?? [];
383+
// may have to uncomment for PHP 8
384+
//$fetch_argument = $args[0] ?? null;
385+
//$ctorargs = $args[1] ?? [];
396386

397387
if ($this->realStatement) {
398388
$this->realStatement->setFetchMode($mode, $fetch_argument, $ctorargs);
@@ -455,10 +445,11 @@ private static function lowercaseKeys(array $row)
455445
* @psalm-taint-sink callable $class
456446
*
457447
* @template T
458-
* @param class-string<T> $class
448+
* @param class-string<T>|null $class
449+
* @param array|null $ctorArgs
459450
* @return false|T
460451
*/
461-
public function fetchObject(?string $class = \stdClass::class, ?array $ctorArgs = null)
452+
public function fetchObject($class = \stdClass::class, $ctorArgs = null)
462453
{
463454
throw new \Exception('not implemented');
464455
}

‎src/Parser/CreateTableParser.php‎

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,32 @@
11
<?php
2+
3+
/**
4+
* Ported to Hack from https://github.com/iamcal/SQLParser
5+
* Ported back to PHP from https://github.com/slackhq/hack-sql-fake
6+
*
7+
* MIT License
8+
*
9+
* Copyright (c) 2013-2017 Cal Henderson
10+
*
11+
* Permission is hereby granted, free of charge, to any person obtaining a copy
12+
* of this software and associated documentation files (the "Software"), to deal
13+
* in the Software without restriction, including without limitation the rights
14+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15+
* copies of the Software, and to permit persons to whom the Software is
16+
* furnished to do so, subject to the following conditions:
17+
*
18+
* The above copyright notice and this permission notice shall be included in all
19+
* copies or substantial portions of the Software.
20+
*
21+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27+
* SOFTWARE.
28+
*/
29+
230
namespace Vimeo\MysqlEngine\Parser;
331

432
use Vimeo\MysqlEngine\TokenType;
@@ -48,7 +76,7 @@ private function lexImpl(string $sql)
4876
$len = \strlen($sql);
4977
$source_map = [];
5078
while ($pos < $len) {
51-
\preg_match("!s+!A", $sql, $matches, 0, $pos);
79+
\preg_match("!\s+!A", $sql, $matches, 0, $pos);
5280
if ($matches) {
5381
$pos += \strlen($matches[0]);
5482
continue;
@@ -62,7 +90,7 @@ private function lexImpl(string $sql)
6290
}
6391
continue;
6492
}
65-
if (\preg_match("!\\*!A", $sql, $matches, 0, $pos)) {
93+
if (\preg_match('!/\\*!A', $sql, $matches, 0, $pos)) {
6694
$p2 = \strpos($sql, "*/", $pos);
6795
if ($p2 === false) {
6896
$pos = $len;
@@ -71,7 +99,7 @@ private function lexImpl(string $sql)
7199
}
72100
continue;
73101
}
74-
\preg_match("![[:alpha:]][[:alnum:]_]*!A", $sql, $matches, 0, $pos);
102+
\preg_match('![[:alpha:]][[:alnum:]_]*!A', $sql, $matches, 0, $pos);
75103
if ($matches) {
76104
$source_map[] = [$pos, \strlen($matches[0])];
77105
$pos += \strlen($matches[0]);
@@ -87,7 +115,7 @@ private function lexImpl(string $sql)
87115
}
88116
continue;
89117
}
90-
$match = \preg_match("!(d+.?d*|.d+)!A", $sql, $matches, 0, $pos);
118+
$match = \preg_match('!(\d+\.?\d*|\.\d+)!A', $sql, $matches, 0, $pos);
91119
if ($matches) {
92120
$source_map[] = [$pos, \strlen($matches[0])];
93121
$pos += \strlen($matches[0]);
@@ -128,7 +156,7 @@ private static function walk(array $tokens, string $sql, array $source_map)
128156
$statements = [];
129157
$temp = [];
130158
$start = 0;
131-
for ($i = 0; $i < \count($tokens); $i++) {
159+
foreach ($tokens as $i => $t) {
132160
$t = $tokens[$i];
133161
if ($t === ';') {
134162
if (\count($temp)) {
@@ -757,7 +785,7 @@ private static function parseIndexColumns(array &$tokens, CreateIndex $index)
757785
*
758786
* @return void
759787
*/
760-
private static function parseIndexOptions(array $tokens, CreateIndex $index)
788+
private static function parseIndexOptions(array &$tokens, CreateIndex $index)
761789
{
762790
if (($tokens) && $tokens[0] === 'KEY_BLOCK_SIZE') {
763791
\array_shift($tokens);
@@ -782,7 +810,7 @@ private static function parseIndexOptions(array $tokens, CreateIndex $index)
782810
*
783811
* @return void
784812
*/
785-
private static function parseFieldLength(array $tokens, MysqlColumnType $t)
813+
private static function parseFieldLength(array &$tokens, MysqlColumnType $t)
786814
{
787815
if (($tokens) && $tokens[0] === '(' && $tokens[2] === ')') {
788816
$t->length = (int) $tokens[1];
@@ -795,7 +823,7 @@ private static function parseFieldLength(array $tokens, MysqlColumnType $t)
795823
*
796824
* @return void
797825
*/
798-
private static function parseFieldLengthDecimals(array $tokens, MysqlColumnType $t)
826+
private static function parseFieldLengthDecimals(array &$tokens, MysqlColumnType $t)
799827
{
800828
if (($tokens) && $tokens[0] === '(' && $tokens[2] === ',' && $tokens[4] === ')') {
801829
$t->length = (int) $tokens[1];
@@ -809,7 +837,7 @@ private static function parseFieldLengthDecimals(array $tokens, MysqlColumnType
809837
*
810838
* @return void
811839
*/
812-
private static function parseFieldUnsigned(array $tokens, MysqlColumnType $t)
840+
private static function parseFieldUnsigned(array &$tokens, MysqlColumnType $t)
813841
{
814842
if (($tokens) && \strtoupper($tokens[0]) === 'UNSIGNED') {
815843
$t->unsigned = true;
@@ -863,7 +891,7 @@ private static function parseFieldCollate(array &$tokens, MysqlColumnType $t)
863891
*
864892
* @return array<int, string>|null
865893
*/
866-
private static function parseValueList(array $tokens)
894+
private static function parseValueList(array &$tokens)
867895
{
868896
if (!($tokens) || $tokens[0] !== '(') {
869897
return null;

0 commit comments

Comments
 (0)