Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -686,22 +686,27 @@ parameters:
path: src/Statements/DeleteStatement.php

-
message: "#^Cannot access property \\$keyword on PhpMyAdmin\\\\SqlParser\\\\Token\\|null\\.$#"
message: "#^Binary operation \"\\.\" between ' ' and array\\<string\\>\\|string results in an error\\.$#"
count: 1
path: src/Statements/ExplainStatement.php

-
message: "#^Cannot access property \\$options on PhpMyAdmin\\\\SqlParser\\\\Components\\\\OptionsArray\\|null\\.$#"
message: "#^Binary operation \"\\.\" between array\\<string\\>\\|string and '\\.' results in an error\\.$#"
count: 1
path: src/Statements/ExplainStatement.php

-
message: "#^Cannot access property \\$value on PhpMyAdmin\\\\SqlParser\\\\Token\\|null\\.$#"
message: "#^Binary operation \"\\.\\=\" between string and array\\<string\\>\\|string results in an error\\.$#"
count: 1
path: src/Statements/ExplainStatement.php

-
message: "#^Parameter \\#1 \\$component of static method PhpMyAdmin\\\\SqlParser\\\\Components\\\\OptionsArray\\:\\:build\\(\\) expects PhpMyAdmin\\\\SqlParser\\\\Components\\\\OptionsArray, PhpMyAdmin\\\\SqlParser\\\\Components\\\\OptionsArray\\|null given\\.$#"
message: "#^Cannot access property \\$keyword on PhpMyAdmin\\\\SqlParser\\\\Token\\|null\\.$#"
count: 1
path: src/Statements/ExplainStatement.php

-
message: "#^Cannot access property \\$value on PhpMyAdmin\\\\SqlParser\\\\Token\\|null\\.$#"
count: 1
path: src/Statements/ExplainStatement.php

Expand All @@ -710,6 +715,16 @@ parameters:
count: 1
path: src/Statements/ExplainStatement.php

-
message: "#^Property PhpMyAdmin\\\\SqlParser\\\\Statements\\\\ExplainStatement\\:\\:\\$explainedColumn \\(string\\|null\\) does not accept mixed\\.$#"
count: 1
path: src/Statements/ExplainStatement.php

-
message: "#^Property PhpMyAdmin\\\\SqlParser\\\\Statements\\\\ExplainStatement\\:\\:\\$explainedDatabase \\(string\\|null\\) does not accept mixed\\.$#"
count: 1
path: src/Statements/ExplainStatement.php

-
message: "#^Property PhpMyAdmin\\\\SqlParser\\\\Statements\\\\ExplainStatement\\:\\:\\$explainedTable \\(string\\|null\\) does not accept mixed\\.$#"
count: 1
Expand Down
17 changes: 10 additions & 7 deletions psalm-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1004,18 +1004,21 @@
</PossiblyNullIterator>
</file>
<file src="src/Statements/ExplainStatement.php">
<MixedAssignment occurrences="2">
<MixedAssignment occurrences="4">
<code>$this-&gt;connectionId</code>
<code>$this-&gt;explainedColumn</code>
<code>$this-&gt;explainedDatabase</code>
<code>$this-&gt;explainedTable</code>
</MixedAssignment>
<PossiblyNullArgument occurrences="2">
<code>$this-&gt;options</code>
<code>$this-&gt;options-&gt;options</code>
</PossiblyNullArgument>
<PossiblyNullPropertyFetch occurrences="3">
<PossiblyInvalidOperand occurrences="4">
<code>Context::escape($this-&gt;explainedColumn)</code>
<code>Context::escape($this-&gt;explainedDatabase)</code>
<code>Context::escape($this-&gt;explainedTable)</code>
<code>Context::escape($this-&gt;explainedTable)</code>
</PossiblyInvalidOperand>
<PossiblyNullPropertyFetch occurrences="2">
<code>$nextToken-&gt;keyword</code>
<code>$nextToken-&gt;value</code>
<code>$this-&gt;options-&gt;options</code>
</PossiblyNullPropertyFetch>
<PropertyNotSetInConstructor occurrences="1">
<code>$statementAlias</code>
Expand Down
101 changes: 89 additions & 12 deletions src/Statements/ExplainStatement.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace PhpMyAdmin\SqlParser\Statements;

use PhpMyAdmin\SqlParser\Components\OptionsArray;
use PhpMyAdmin\SqlParser\Context;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Statement;
use PhpMyAdmin\SqlParser\Token;
Expand Down Expand Up @@ -58,13 +59,27 @@ class ExplainStatement extends Statement
*/
public $connectionId = null;

/**
* The explained database for the table's name, if used.
*
* @var string|null
*/
public $explainedDatabase = null;

/**
* The explained table's name, if used.
*
* @var string|null
*/
public $explainedTable = null;

/**
* The explained column's name, if used.
*
* @var string|null
*/
public $explainedColumn = null;

/**
* @param Parser $parser the instance that requests parsing
* @param TokensList $list the list of tokens to be parsed
Expand All @@ -78,10 +93,14 @@ public function parse(Parser $parser, TokensList $list)
*
* 0 -------------------[ EXPLAIN/EXPLAIN ANALYZE/ANALYZE ]-----------------------> 1
*
* 0 ------------------------[ EXPLAIN/DESC/DESCRIBE ]----------------------------> 3
*
* 1 ------------------------------[ OPTIONS ]------------------------------------> 2
*
* 2 --------------[ tablename / STATEMENT / FOR CONNECTION ]---------------------> 2
*
* 3 -----------------------------[ tablename ]-----------------------------------> 3
*
* @var int
*/
$state = 0;
Expand All @@ -100,6 +119,12 @@ public function parse(Parser $parser, TokensList $list)
*/
$token = $list->tokens[$list->idx];

// End of statement.
if ($token->type === Token::TYPE_DELIMITER) {
--$list->idx; // Back up one token, no real reasons to document
break;
}

// Skipping whitespaces and comments.
if ($token->type === Token::TYPE_WHITESPACE || $token->type === Token::TYPE_COMMENT) {
continue;
Expand All @@ -114,9 +139,21 @@ public function parse(Parser $parser, TokensList $list)
|| $token->keyword === 'DESC'
|| $token->keyword === 'DESCRIBE'
) {
$miniState = 1;
$this->statementAlias = $token->keyword;

$lastIdx = $list->idx;
$list->idx++; // Ignore the current token
$nextKeyword = $list->getNextOfType(Token::TYPE_KEYWORD);
$list->idx = $lastIdx;

// There is no other keyword, we must be describing a table
if ($nextKeyword === null) {
$state = 3;
continue;
}

$miniState = 1;

$lastIdx = $list->idx;
$nextKeyword = $list->getNextOfTypeAndValue(Token::TYPE_KEYWORD, 'ANALYZE');
if ($nextKeyword && $nextKeyword->keyword !== null) {
Expand Down Expand Up @@ -146,12 +183,6 @@ public function parse(Parser $parser, TokensList $list)
break;
}

// To support EXPLAIN tablename
if ($token->type === Token::TYPE_NONE) {
$this->explainedTable = $token->value;
break;
}

if (
$token->keyword !== 'SELECT'
&& $token->keyword !== 'TABLE'
Expand Down Expand Up @@ -180,28 +211,74 @@ public function parse(Parser $parser, TokensList $list)

$list->idx = $idxOfLastParsedToken;
break;
} elseif ($state === 3) {
if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '.')) {
continue;
}

if ($this->explainedDatabase === null) {
$lastIdx = $list->idx;
$nextDot = $list->getNextOfTypeAndValue(Token::TYPE_OPERATOR, '.');
$list->idx = $lastIdx;
if ($nextDot !== null) {// We found a dot, so it must be a db.table name format
$this->explainedDatabase = $token->value;
continue;
}
}

if ($this->explainedTable === null) {
$this->explainedTable = $token->value;
continue;
}

if ($this->explainedColumn === null) {
$this->explainedColumn = $token->value;
}
}
}

if ($state !== 3 || $this->explainedTable !== null) {
return;
}

// We reached end of the state 3 and no table name was found
/** Token parsed at this moment. */
$token = $list->tokens[$list->idx];
$parser->error('Expected a table name.', $token);
}

public function build(): string
{
$str = $this->statementAlias;

if (count($this->options->options)) {
$str .= ' ';
if ($this->options !== null) {
if (count($this->options->options)) {
$str .= ' ';
}

$str .= OptionsArray::build($this->options) . ' ';
}

$str .= OptionsArray::build($this->options) . ' ';
if ($this->options === null) {
$str .= ' ';
}

if ($this->bodyParser) {
foreach ($this->bodyParser->statements as $statement) {
$str .= $statement->build();
}
} elseif ($this->connectionId) {
$str .= 'FOR CONNECTION ' . $this->connectionId;
} elseif ($this->explainedTable) {
$str .= $this->explainedTable;
}

if ($this->explainedDatabase !== null && $this->explainedTable !== null) {
$str .= Context::escape($this->explainedDatabase) . '.' . Context::escape($this->explainedTable);
} elseif ($this->explainedTable !== null) {
$str .= Context::escape($this->explainedTable);
}

if ($this->explainedColumn !== null) {
$str .= ' ' . Context::escape($this->explainedColumn);
}

return $str;
Expand Down
31 changes: 29 additions & 2 deletions tests/Builder/ExplainStatementTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public function testBuilder(): void
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
'DESCRIBE tablename',
'DESCRIBE `tablename`',
$stmt->build()
);

Expand All @@ -65,13 +65,40 @@ public function testBuilder(): void
$stmt->build()
);

/* Assertion 6 */
/* Assertion 7 */
$query = 'EXPLAIN FORMAT=TREE SELECT * FROM db;';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
'EXPLAIN FORMAT=TREE SELECT * FROM db',
$stmt->build()
);

/* Assertion 8 */
$query = 'DESCRIBE tablename colname;';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
'DESCRIBE `tablename` `colname`',
$stmt->build()
);

/* Assertion 9 */
$query = 'DESCRIBE tablename \'col%me\';';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
'DESCRIBE `tablename` `col%me`',
$stmt->build()
);

/* Assertion 9 */
$query = 'DESCRIBE db.tablename \'col%me\';';
$parser = new Parser($query);
$stmt = $parser->statements[0];
$this->assertEquals(
'DESCRIBE `db`.`tablename` `col%me`',
$stmt->build()
);
}
}
2 changes: 2 additions & 0 deletions tests/data/parser/parseAnalyzeErr1.out
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@
"bodyParser": null,
"statementAlias": "ANALYZE",
"connectionId": null,
"explainedDatabase": null,
"explainedTable": null,
"explainedColumn": null,
"options": {
"@type": "PhpMyAdmin\\SqlParser\\Components\\OptionsArray",
"options": []
Expand Down
19 changes: 5 additions & 14 deletions tests/data/parser/parseAnalyzeErr2.out
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,12 @@
"bodyParser": null,
"statementAlias": "ANALYZE",
"connectionId": null,
"explainedDatabase": null,
"explainedTable": null,
"options": {
"@type": "PhpMyAdmin\\SqlParser\\Components\\OptionsArray",
"options": []
},
"explainedColumn": null,
"options": null,
"first": 0,
"last": 1
"last": 0
}
],
"brackets": 0,
Expand All @@ -61,14 +60,6 @@
},
"errors": {
"lexer": [],
"parser": [
[
"Unexpected token.",
{
"@type": "@3"
},
0
]
]
"parser": []
}
}
2 changes: 2 additions & 0 deletions tests/data/parser/parseExplain.out
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,9 @@
},
"statementAlias": "EXPLAIN",
"connectionId": null,
"explainedDatabase": null,
"explainedTable": null,
"explainedColumn": null,
"options": {
"@type": "PhpMyAdmin\\SqlParser\\Components\\OptionsArray",
"options": []
Expand Down
2 changes: 2 additions & 0 deletions tests/data/parser/parseExplain1.out
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,9 @@
},
"statementAlias": "ANALYZE",
"connectionId": null,
"explainedDatabase": null,
"explainedTable": null,
"explainedColumn": null,
"options": {
"@type": "PhpMyAdmin\\SqlParser\\Components\\OptionsArray",
"options": []
Expand Down
2 changes: 2 additions & 0 deletions tests/data/parser/parseExplain10.out
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,9 @@
},
"statementAlias": "DESC",
"connectionId": null,
"explainedDatabase": null,
"explainedTable": null,
"explainedColumn": null,
"options": {
"@type": "PhpMyAdmin\\SqlParser\\Components\\OptionsArray",
"options": []
Expand Down
2 changes: 2 additions & 0 deletions tests/data/parser/parseExplain12.out
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,9 @@
},
"statementAlias": "EXPLAIN",
"connectionId": null,
"explainedDatabase": null,
"explainedTable": null,
"explainedColumn": null,
"options": {
"@type": "PhpMyAdmin\\SqlParser\\Components\\OptionsArray",
"options": {
Expand Down
Loading