Skip to content

Commit 59a8c8b

Browse files
committed
Merge #446 - Fix #445 - re-design part of DESCRIBE
Pull-request: #446 Fixes: #445 Signed-off-by: William Desportes <williamdes@wdes.fr>
2 parents be57b5c + 1420af7 commit 59a8c8b

24 files changed

+211
-121
lines changed

phpstan-baseline.neon

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -686,22 +686,27 @@ parameters:
686686
path: src/Statements/DeleteStatement.php
687687

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

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

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

703703
-
704-
message: "#^Parameter \\#1 \\$component of static method PhpMyAdmin\\\\SqlParser\\\\Components\\\\OptionsArray\\:\\:build\\(\\) expects PhpMyAdmin\\\\SqlParser\\\\Components\\\\OptionsArray, PhpMyAdmin\\\\SqlParser\\\\Components\\\\OptionsArray\\|null given\\.$#"
704+
message: "#^Cannot access property \\$keyword on PhpMyAdmin\\\\SqlParser\\\\Token\\|null\\.$#"
705+
count: 1
706+
path: src/Statements/ExplainStatement.php
707+
708+
-
709+
message: "#^Cannot access property \\$value on PhpMyAdmin\\\\SqlParser\\\\Token\\|null\\.$#"
705710
count: 1
706711
path: src/Statements/ExplainStatement.php
707712

@@ -710,6 +715,16 @@ parameters:
710715
count: 1
711716
path: src/Statements/ExplainStatement.php
712717

718+
-
719+
message: "#^Property PhpMyAdmin\\\\SqlParser\\\\Statements\\\\ExplainStatement\\:\\:\\$explainedColumn \\(string\\|null\\) does not accept mixed\\.$#"
720+
count: 1
721+
path: src/Statements/ExplainStatement.php
722+
723+
-
724+
message: "#^Property PhpMyAdmin\\\\SqlParser\\\\Statements\\\\ExplainStatement\\:\\:\\$explainedDatabase \\(string\\|null\\) does not accept mixed\\.$#"
725+
count: 1
726+
path: src/Statements/ExplainStatement.php
727+
713728
-
714729
message: "#^Property PhpMyAdmin\\\\SqlParser\\\\Statements\\\\ExplainStatement\\:\\:\\$explainedTable \\(string\\|null\\) does not accept mixed\\.$#"
715730
count: 1

psalm-baseline.xml

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,18 +1004,21 @@
10041004
</PossiblyNullIterator>
10051005
</file>
10061006
<file src="src/Statements/ExplainStatement.php">
1007-
<MixedAssignment occurrences="2">
1007+
<MixedAssignment occurrences="4">
10081008
<code>$this-&gt;connectionId</code>
1009+
<code>$this-&gt;explainedColumn</code>
1010+
<code>$this-&gt;explainedDatabase</code>
10091011
<code>$this-&gt;explainedTable</code>
10101012
</MixedAssignment>
1011-
<PossiblyNullArgument occurrences="2">
1012-
<code>$this-&gt;options</code>
1013-
<code>$this-&gt;options-&gt;options</code>
1014-
</PossiblyNullArgument>
1015-
<PossiblyNullPropertyFetch occurrences="3">
1013+
<PossiblyInvalidOperand occurrences="4">
1014+
<code>Context::escape($this-&gt;explainedColumn)</code>
1015+
<code>Context::escape($this-&gt;explainedDatabase)</code>
1016+
<code>Context::escape($this-&gt;explainedTable)</code>
1017+
<code>Context::escape($this-&gt;explainedTable)</code>
1018+
</PossiblyInvalidOperand>
1019+
<PossiblyNullPropertyFetch occurrences="2">
10161020
<code>$nextToken-&gt;keyword</code>
10171021
<code>$nextToken-&gt;value</code>
1018-
<code>$this-&gt;options-&gt;options</code>
10191022
</PossiblyNullPropertyFetch>
10201023
<PropertyNotSetInConstructor occurrences="1">
10211024
<code>$statementAlias</code>

src/Statements/ExplainStatement.php

Lines changed: 89 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace PhpMyAdmin\SqlParser\Statements;
66

77
use PhpMyAdmin\SqlParser\Components\OptionsArray;
8+
use PhpMyAdmin\SqlParser\Context;
89
use PhpMyAdmin\SqlParser\Parser;
910
use PhpMyAdmin\SqlParser\Statement;
1011
use PhpMyAdmin\SqlParser\Token;
@@ -58,13 +59,27 @@ class ExplainStatement extends Statement
5859
*/
5960
public $connectionId = null;
6061

62+
/**
63+
* The explained database for the table's name, if used.
64+
*
65+
* @var string|null
66+
*/
67+
public $explainedDatabase = null;
68+
6169
/**
6270
* The explained table's name, if used.
6371
*
6472
* @var string|null
6573
*/
6674
public $explainedTable = null;
6775

76+
/**
77+
* The explained column's name, if used.
78+
*
79+
* @var string|null
80+
*/
81+
public $explainedColumn = null;
82+
6883
/**
6984
* @param Parser $parser the instance that requests parsing
7085
* @param TokensList $list the list of tokens to be parsed
@@ -78,10 +93,14 @@ public function parse(Parser $parser, TokensList $list)
7893
*
7994
* 0 -------------------[ EXPLAIN/EXPLAIN ANALYZE/ANALYZE ]-----------------------> 1
8095
*
96+
* 0 ------------------------[ EXPLAIN/DESC/DESCRIBE ]----------------------------> 3
97+
*
8198
* 1 ------------------------------[ OPTIONS ]------------------------------------> 2
8299
*
83100
* 2 --------------[ tablename / STATEMENT / FOR CONNECTION ]---------------------> 2
84101
*
102+
* 3 -----------------------------[ tablename ]-----------------------------------> 3
103+
*
85104
* @var int
86105
*/
87106
$state = 0;
@@ -100,6 +119,12 @@ public function parse(Parser $parser, TokensList $list)
100119
*/
101120
$token = $list->tokens[$list->idx];
102121

122+
// End of statement.
123+
if ($token->type === Token::TYPE_DELIMITER) {
124+
--$list->idx; // Back up one token, no real reasons to document
125+
break;
126+
}
127+
103128
// Skipping whitespaces and comments.
104129
if ($token->type === Token::TYPE_WHITESPACE || $token->type === Token::TYPE_COMMENT) {
105130
continue;
@@ -114,9 +139,21 @@ public function parse(Parser $parser, TokensList $list)
114139
|| $token->keyword === 'DESC'
115140
|| $token->keyword === 'DESCRIBE'
116141
) {
117-
$miniState = 1;
118142
$this->statementAlias = $token->keyword;
119143

144+
$lastIdx = $list->idx;
145+
$list->idx++; // Ignore the current token
146+
$nextKeyword = $list->getNextOfType(Token::TYPE_KEYWORD);
147+
$list->idx = $lastIdx;
148+
149+
// There is no other keyword, we must be describing a table
150+
if ($nextKeyword === null) {
151+
$state = 3;
152+
continue;
153+
}
154+
155+
$miniState = 1;
156+
120157
$lastIdx = $list->idx;
121158
$nextKeyword = $list->getNextOfTypeAndValue(Token::TYPE_KEYWORD, 'ANALYZE');
122159
if ($nextKeyword && $nextKeyword->keyword !== null) {
@@ -146,12 +183,6 @@ public function parse(Parser $parser, TokensList $list)
146183
break;
147184
}
148185

149-
// To support EXPLAIN tablename
150-
if ($token->type === Token::TYPE_NONE) {
151-
$this->explainedTable = $token->value;
152-
break;
153-
}
154-
155186
if (
156187
$token->keyword !== 'SELECT'
157188
&& $token->keyword !== 'TABLE'
@@ -180,28 +211,74 @@ public function parse(Parser $parser, TokensList $list)
180211

181212
$list->idx = $idxOfLastParsedToken;
182213
break;
214+
} elseif ($state === 3) {
215+
if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '.')) {
216+
continue;
217+
}
218+
219+
if ($this->explainedDatabase === null) {
220+
$lastIdx = $list->idx;
221+
$nextDot = $list->getNextOfTypeAndValue(Token::TYPE_OPERATOR, '.');
222+
$list->idx = $lastIdx;
223+
if ($nextDot !== null) {// We found a dot, so it must be a db.table name format
224+
$this->explainedDatabase = $token->value;
225+
continue;
226+
}
227+
}
228+
229+
if ($this->explainedTable === null) {
230+
$this->explainedTable = $token->value;
231+
continue;
232+
}
233+
234+
if ($this->explainedColumn === null) {
235+
$this->explainedColumn = $token->value;
236+
}
183237
}
184238
}
239+
240+
if ($state !== 3 || $this->explainedTable !== null) {
241+
return;
242+
}
243+
244+
// We reached end of the state 3 and no table name was found
245+
/** Token parsed at this moment. */
246+
$token = $list->tokens[$list->idx];
247+
$parser->error('Expected a table name.', $token);
185248
}
186249

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

191-
if (count($this->options->options)) {
192-
$str .= ' ';
254+
if ($this->options !== null) {
255+
if (count($this->options->options)) {
256+
$str .= ' ';
257+
}
258+
259+
$str .= OptionsArray::build($this->options) . ' ';
193260
}
194261

195-
$str .= OptionsArray::build($this->options) . ' ';
262+
if ($this->options === null) {
263+
$str .= ' ';
264+
}
196265

197266
if ($this->bodyParser) {
198267
foreach ($this->bodyParser->statements as $statement) {
199268
$str .= $statement->build();
200269
}
201270
} elseif ($this->connectionId) {
202271
$str .= 'FOR CONNECTION ' . $this->connectionId;
203-
} elseif ($this->explainedTable) {
204-
$str .= $this->explainedTable;
272+
}
273+
274+
if ($this->explainedDatabase !== null && $this->explainedTable !== null) {
275+
$str .= Context::escape($this->explainedDatabase) . '.' . Context::escape($this->explainedTable);
276+
} elseif ($this->explainedTable !== null) {
277+
$str .= Context::escape($this->explainedTable);
278+
}
279+
280+
if ($this->explainedColumn !== null) {
281+
$str .= ' ' . Context::escape($this->explainedColumn);
205282
}
206283

207284
return $str;

tests/Builder/ExplainStatementTest.php

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public function testBuilder(): void
5252
$parser = new Parser($query);
5353
$stmt = $parser->statements[0];
5454
$this->assertEquals(
55-
'DESCRIBE tablename',
55+
'DESCRIBE `tablename`',
5656
$stmt->build()
5757
);
5858

@@ -65,13 +65,40 @@ public function testBuilder(): void
6565
$stmt->build()
6666
);
6767

68-
/* Assertion 6 */
68+
/* Assertion 7 */
6969
$query = 'EXPLAIN FORMAT=TREE SELECT * FROM db;';
7070
$parser = new Parser($query);
7171
$stmt = $parser->statements[0];
7272
$this->assertEquals(
7373
'EXPLAIN FORMAT=TREE SELECT * FROM db',
7474
$stmt->build()
7575
);
76+
77+
/* Assertion 8 */
78+
$query = 'DESCRIBE tablename colname;';
79+
$parser = new Parser($query);
80+
$stmt = $parser->statements[0];
81+
$this->assertEquals(
82+
'DESCRIBE `tablename` `colname`',
83+
$stmt->build()
84+
);
85+
86+
/* Assertion 9 */
87+
$query = 'DESCRIBE tablename \'col%me\';';
88+
$parser = new Parser($query);
89+
$stmt = $parser->statements[0];
90+
$this->assertEquals(
91+
'DESCRIBE `tablename` `col%me`',
92+
$stmt->build()
93+
);
94+
95+
/* Assertion 9 */
96+
$query = 'DESCRIBE db.tablename \'col%me\';';
97+
$parser = new Parser($query);
98+
$stmt = $parser->statements[0];
99+
$this->assertEquals(
100+
'DESCRIBE `db`.`tablename` `col%me`',
101+
$stmt->build()
102+
);
76103
}
77104
}

tests/data/parser/parseAnalyzeErr1.out

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,9 @@
6464
"bodyParser": null,
6565
"statementAlias": "ANALYZE",
6666
"connectionId": null,
67+
"explainedDatabase": null,
6768
"explainedTable": null,
69+
"explainedColumn": null,
6870
"options": {
6971
"@type": "PhpMyAdmin\\SqlParser\\Components\\OptionsArray",
7072
"options": []

tests/data/parser/parseAnalyzeErr2.out

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,12 @@
4646
"bodyParser": null,
4747
"statementAlias": "ANALYZE",
4848
"connectionId": null,
49+
"explainedDatabase": null,
4950
"explainedTable": null,
50-
"options": {
51-
"@type": "PhpMyAdmin\\SqlParser\\Components\\OptionsArray",
52-
"options": []
53-
},
51+
"explainedColumn": null,
52+
"options": null,
5453
"first": 0,
55-
"last": 1
54+
"last": 0
5655
}
5756
],
5857
"brackets": 0,
@@ -61,14 +60,6 @@
6160
},
6261
"errors": {
6362
"lexer": [],
64-
"parser": [
65-
[
66-
"Unexpected token.",
67-
{
68-
"@type": "@3"
69-
},
70-
0
71-
]
72-
]
63+
"parser": []
7364
}
7465
}

tests/data/parser/parseExplain.out

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,9 @@
214214
},
215215
"statementAlias": "EXPLAIN",
216216
"connectionId": null,
217+
"explainedDatabase": null,
217218
"explainedTable": null,
219+
"explainedColumn": null,
218220
"options": {
219221
"@type": "PhpMyAdmin\\SqlParser\\Components\\OptionsArray",
220222
"options": []

tests/data/parser/parseExplain1.out

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,9 @@
202202
},
203203
"statementAlias": "ANALYZE",
204204
"connectionId": null,
205+
"explainedDatabase": null,
205206
"explainedTable": null,
207+
"explainedColumn": null,
206208
"options": {
207209
"@type": "PhpMyAdmin\\SqlParser\\Components\\OptionsArray",
208210
"options": []

tests/data/parser/parseExplain10.out

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,9 @@
333333
},
334334
"statementAlias": "DESC",
335335
"connectionId": null,
336+
"explainedDatabase": null,
336337
"explainedTable": null,
338+
"explainedColumn": null,
337339
"options": {
338340
"@type": "PhpMyAdmin\\SqlParser\\Components\\OptionsArray",
339341
"options": []

tests/data/parser/parseExplain12.out

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,9 @@
369369
},
370370
"statementAlias": "EXPLAIN",
371371
"connectionId": null,
372+
"explainedDatabase": null,
372373
"explainedTable": null,
374+
"explainedColumn": null,
373375
"options": {
374376
"@type": "PhpMyAdmin\\SqlParser\\Components\\OptionsArray",
375377
"options": {

0 commit comments

Comments
 (0)