55namespace PhpMyAdmin \SqlParser \Statements ;
66
77use PhpMyAdmin \SqlParser \Components \OptionsArray ;
8+ use PhpMyAdmin \SqlParser \Context ;
89use PhpMyAdmin \SqlParser \Parser ;
910use PhpMyAdmin \SqlParser \Statement ;
1011use 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 ;
0 commit comments