66
77use AllowDynamicProperties ;
88use PhpMyAdmin \SqlParser \Components \OptionsArray ;
9+ use PhpMyAdmin \SqlParser \Exceptions \ParserException ;
910use PhpMyAdmin \SqlParser \Parsers \OptionsArrays ;
11+ use PhpMyAdmin \SqlParser \Statements \SelectStatement ;
12+ use PhpMyAdmin \SqlParser \Statements \SetStatement ;
13+ use PhpMyAdmin \SqlParser \Utils \Query ;
1014use Stringable ;
1115
1216use function array_flip ;
1317use function array_key_exists ;
1418use function array_keys ;
15- use function in_array ;
1619use function is_array ;
17- use function stripos ;
20+ use function is_string ;
21+ use function str_contains ;
22+ use function strtoupper ;
1823use function trim ;
1924
2025/**
@@ -79,6 +84,8 @@ abstract class Statement implements Stringable
7984 /**
8085 * @param Parser|null $parser the instance that requests parsing
8186 * @param TokensList|null $list the list of tokens to be parsed
87+ *
88+ * @throws ParserException
8289 */
8390 public function __construct (Parser |null $ parser = null , TokensList |null $ list = null )
8491 {
@@ -159,7 +166,7 @@ public function build(): string
159166 * @param Parser $parser the instance that requests parsing
160167 * @param TokensList $list the list of tokens to be parsed
161168 *
162- * @throws Exceptions\ ParserException
169+ * @throws ParserException
163170 */
164171 public function parse (Parser $ parser , TokensList $ list ): void
165172 {
@@ -224,7 +231,7 @@ public function parse(Parser $parser, TokensList $list): void
224231 // ON DUPLICATE KEY UPDATE ...
225232 // has to be parsed in parent statement (INSERT or REPLACE)
226233 // so look for it and break
227- if ($ this instanceof Statements \ SelectStatement && $ token ->value === 'ON ' ) {
234+ if ($ this instanceof SelectStatement && $ token ->value === 'ON ' ) {
228235 ++$ list ->idx ; // Skip ON
229236
230237 // look for ON DUPLICATE KEY UPDATE
@@ -261,8 +268,17 @@ public function parse(Parser $parser, TokensList $list): void
261268 $ options = [];
262269
263270 // Looking for duplicated clauses.
264- if (isset (Parser::KEYWORD_PARSERS [$ token ->value ]) || ! empty (Parser::STATEMENT_PARSERS [$ token ->value ])) {
265- if (! empty ($ parsedClauses [$ token ->value ])) {
271+ if (
272+ is_string ($ token ->value )
273+ && (
274+ isset (Parser::KEYWORD_PARSERS [$ token ->value ])
275+ || (
276+ isset (Parser::STATEMENT_PARSERS [$ token ->value ])
277+ && Parser::STATEMENT_PARSERS [$ token ->value ] !== ''
278+ )
279+ )
280+ ) {
281+ if (array_key_exists ($ token ->value , $ parsedClauses )) {
266282 $ parser ->error ('This type of clause was previously parsed. ' , $ token );
267283 break ;
268284 }
@@ -271,27 +287,27 @@ public function parse(Parser $parser, TokensList $list): void
271287 }
272288
273289 // Checking if this is the beginning of a clause.
274- // Fix Issue #221: As `truncate` is not a keyword
290+ // Fix Issue #221: As `truncate` is not a keyword,
275291 // but it might be the beginning of a statement of truncate,
276292 // so let the value use the keyword field for truncate type.
277- $ tokenValue = in_array ( $ token ->keyword , [ 'TRUNCATE ' ]) ? $ token ->keyword : $ token ->value ;
278- if (isset (Parser::KEYWORD_PARSERS [$ tokenValue ]) && $ list ->idx < $ list ->count ) {
293+ $ tokenValue = $ token ->keyword === 'TRUNCATE ' ? $ token ->keyword : $ token ->value ;
294+ if (is_string ( $ tokenValue ) && isset (Parser::KEYWORD_PARSERS [$ tokenValue ]) && $ list ->idx < $ list ->count ) {
279295 $ class = Parser::KEYWORD_PARSERS [$ tokenValue ]['class ' ];
280296 $ field = Parser::KEYWORD_PARSERS [$ tokenValue ]['field ' ];
281- if (! empty (Parser::KEYWORD_PARSERS [$ tokenValue ]['options ' ])) {
297+ if (isset (Parser::KEYWORD_PARSERS [$ tokenValue ]['options ' ])) {
282298 $ options = Parser::KEYWORD_PARSERS [$ tokenValue ]['options ' ];
283299 }
284300 }
285301
286302 // Checking if this is the beginning of the statement.
287- if (! empty (Parser:: STATEMENT_PARSERS [ $ token -> keyword ])) {
288- if (
289- ! empty ( static :: $ clauses ) // Undefined for some statements.
290- && empty ( static :: $ clauses [ $ token -> value ])
291- ) {
303+ if (
304+ isset (Parser:: STATEMENT_PARSERS [ $ token -> keyword ])
305+ && Parser:: STATEMENT_PARSERS [ $ token -> keyword ] !== ''
306+ ) {
307+ if ( static :: $ clauses !== [] && is_string ( $ token -> value ) && ! isset ( static :: $ clauses [ $ token -> value ]) ) {
292308 // Some keywords (e.g. `SET`) may be the beginning of a
293309 // statement and a clause.
294- // If such keyword was found and it cannot be a clause of
310+ // If such keyword was found, and it cannot be a clause of
295311 // this statement it means it is a new statement, but no
296312 // delimiter was found between them.
297313 $ parser ->error (
@@ -311,35 +327,27 @@ public function parse(Parser $parser, TokensList $list): void
311327 $ parsedOptions = true ;
312328 }
313329 } elseif ($ class === null ) {
314- if ($ this instanceof Statements \ SelectStatement && $ token ->value === 'WITH ROLLUP ' ) {
330+ if ($ this instanceof SelectStatement && $ token ->value === 'WITH ROLLUP ' ) {
315331 // Handle group options in Select statement
316332 $ this ->groupOptions = OptionsArrays::parse (
317333 $ parser ,
318334 $ list ,
319- Statements \ SelectStatement::STATEMENT_GROUP_OPTIONS ,
335+ SelectStatement::STATEMENT_GROUP_OPTIONS ,
320336 );
321337 } elseif (
322- $ this instanceof Statements \ SelectStatement
338+ $ this instanceof SelectStatement
323339 && ($ token ->value === 'FOR UPDATE '
324340 || $ token ->value === 'LOCK IN SHARE MODE ' )
325341 ) {
326342 // Handle special end options in Select statement
327- $ this ->endOptions = OptionsArrays::parse (
328- $ parser ,
329- $ list ,
330- Statements \SelectStatement::STATEMENT_END_OPTIONS ,
331- );
343+ $ this ->endOptions = OptionsArrays::parse ($ parser , $ list , SelectStatement::STATEMENT_END_OPTIONS );
332344 } elseif (
333- $ this instanceof Statements \ SetStatement
345+ $ this instanceof SetStatement
334346 && ($ token ->value === 'COLLATE '
335347 || $ token ->value === 'DEFAULT ' )
336348 ) {
337349 // Handle special end options in SET statement
338- $ this ->endOptions = OptionsArrays::parse (
339- $ parser ,
340- $ list ,
341- Statements \SetStatement::STATEMENT_END_OPTIONS ,
342- );
350+ $ this ->endOptions = OptionsArrays::parse ($ parser , $ list , SetStatement::STATEMENT_END_OPTIONS );
343351 } else {
344352 // There is no parser for this keyword and isn't the beginning
345353 // of a statement (so no options) either.
@@ -419,7 +427,7 @@ public function __toString(): string
419427 * @param Parser $parser the instance that requests parsing
420428 * @param TokensList $list the list of tokens to be parsed
421429 *
422- * @throws Exceptions\ ParserException
430+ * @throws ParserException
423431 */
424432 public function validateClauseOrder (Parser $ parser , TokensList $ list ): bool
425433 {
@@ -449,12 +457,12 @@ public function validateClauseOrder(Parser $parser, TokensList $list): bool
449457
450458 $ error = 0 ;
451459 $ lastIdx = 0 ;
452- foreach ($ clauses as $ clauseType => $ index ) {
453- $ clauseStartIdx = Utils \ Query::getClauseStartOffset ($ this , $ list , $ clauseType );
460+ foreach (array_keys ( $ clauses) as $ clauseType ) {
461+ $ clauseStartIdx = Query::getClauseStartOffset ($ this , $ list , $ clauseType );
454462
455463 if (
456464 $ clauseStartIdx !== -1
457- && $ this instanceof Statements \ SelectStatement
465+ && $ this instanceof SelectStatement
458466 && ($ clauseType === 'FORCE '
459467 || $ clauseType === 'IGNORE '
460468 || $ clauseType === 'USE ' )
@@ -466,13 +474,14 @@ public function validateClauseOrder(Parser $parser, TokensList $list): bool
466474
467475 // Handle ordering of Multiple Joins in a query
468476 if ($ clauseStartIdx !== -1 ) {
469- if ($ minJoin === 0 && stripos ($ clauseType , 'JOIN ' )) {
477+ $ containsJoinClause = str_contains (strtoupper ($ clauseType ), 'JOIN ' );
478+ if ($ minJoin === 0 && $ containsJoinClause ) {
470479 // First JOIN clause is detected
471480 $ minJoin = $ maxJoin = $ clauseStartIdx ;
472- } elseif ($ minJoin !== 0 && ! stripos ( $ clauseType , ' JOIN ' ) ) {
481+ } elseif ($ minJoin !== 0 && ! $ containsJoinClause ) {
473482 // After a previous JOIN clause, a non-JOIN clause has been detected
474483 $ maxJoin = $ lastIdx ;
475- } elseif ($ maxJoin < $ clauseStartIdx && stripos ( $ clauseType , ' JOIN ' ) ) {
484+ } elseif ($ maxJoin < $ clauseStartIdx && $ containsJoinClause ) {
476485 $ error = 1 ;
477486 }
478487 }
0 commit comments