* Update sqlparser to fit php8.

This commit is contained in:
songchenxuan
2024-03-19 14:52:48 +08:00
parent 39f5d2905b
commit ea516fd9b3
663 changed files with 140347 additions and 16174 deletions
+103 -102
View File
@@ -1,25 +1,28 @@
<?php
/**
* The result of the parser is an array of statements are extensions of the
* class defined here.
*
* A statement represents the result of parsing the lexemes.
*/
declare(strict_types=1);
namespace PhpMyAdmin\SqlParser;
use PhpMyAdmin\SqlParser\Components\FunctionCall;
use PhpMyAdmin\SqlParser\Components\OptionsArray;
use Stringable;
use function array_flip;
use function array_keys;
use function count;
use function in_array;
use function stripos;
use function trim;
/**
* The result of the parser is an array of statements are extensions of the class defined here.
*
* A statement represents the result of parsing the lexemes.
*
* Abstract statement definition.
*
* @category Statements
*
* @license https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
*/
abstract class Statement
#[\AllowDynamicProperties]
abstract class Statement implements Stringable
{
/**
* Options for this statement.
@@ -38,9 +41,10 @@ abstract class Statement
* Two options that can be used together must have different values for
* indexes, else, when they will be used together, an error will occur.
*
* @var array
* @var array<string, int|array<int, int|string>>
* @psalm-var array<string, (positive-int|array{positive-int, ('var'|'var='|'expr'|'expr=')})>
*/
public static $OPTIONS = array();
public static $OPTIONS = [];
/**
* The clauses of this statement, in order.
@@ -52,46 +56,59 @@ abstract class Statement
* - 2 = 10 - add the keyword
* - 3 = 11 - add both the keyword and the clause
*
* @var array
* @var array<string, array<int, int|string>>
* @psalm-var array<string, array{non-empty-string, (1|2|3)}>
*/
public static $CLAUSES = array();
public static $CLAUSES = [];
public static $END_OPTIONS = array();
/**
* Options that can be given to GROUP BY component.
*
* @var array<string, int|array<int, int|string>>
* @psalm-var array<string, (positive-int|array{positive-int, ('var'|'var='|'expr'|'expr=')})>
*/
public static $GROUP_OPTIONS = [];
/**
* @var array<string, int|array<int, int|string>>
* @psalm-var array<string, (positive-int|array{positive-int, ('var'|'var='|'expr'|'expr=')})>
*/
public static $END_OPTIONS = [];
/**
* The options of this query.
*
* @var OptionsArray
*
* @see static::$OPTIONS
*
* @var OptionsArray|null
*/
public $options;
/**
* The index of the first token used in this statement.
*
* @var int
* @var int|null
*/
public $first;
/**
* The index of the last token used in this statement.
*
* @var int
* @var int|null
*/
public $last;
/**
* Constructor.
*
* @param Parser $parser the instance that requests parsing
* @param TokensList $list the list of tokens to be parsed
* @param Parser|null $parser the instance that requests parsing
* @param TokensList|null $list the list of tokens to be parsed
*/
public function __construct(Parser $parser = null, TokensList $list = null)
public function __construct(?Parser $parser = null, ?TokensList $list = null)
{
if (($parser !== null) && ($list !== null)) {
$this->parse($parser, $list);
if (($parser === null) || ($list === null)) {
return;
}
$this->parse($parser, $list);
}
/**
@@ -117,15 +134,11 @@ abstract class Statement
*
* A clause is considered built just after fields' value
* (`$this->field`) was used in building.
*
* @var array
*/
$built = array();
$built = [];
/**
* Statement's clauses.
*
* @var array
*/
$clauses = $this->getClauses();
@@ -171,6 +184,7 @@ abstract class Statement
if (! empty($built[$field])) {
continue;
}
$built[$field] = true;
}
@@ -180,9 +194,11 @@ abstract class Statement
}
// Checking if the result of the builder should be added.
if ($type & 1) {
$query = trim($query) . ' ' . $class::build($this->$field);
if (! ($type & 1)) {
continue;
}
$query = trim($query) . ' ' . $class::build($this->$field);
}
return $query;
@@ -193,6 +209,9 @@ abstract class Statement
*
* @param Parser $parser the instance that requests parsing
* @param TokensList $list the list of tokens to be parsed
*
* @return void
*
* @throws Exceptions\ParserException
*/
public function parse(Parser $parser, TokensList $list)
@@ -200,10 +219,8 @@ abstract class Statement
/**
* Array containing all list of clauses parsed.
* This is used to check for duplicates.
*
* @var array
*/
$parsedClauses = array();
$parsedClauses = [];
// This may be corrected by the parser.
$this->first = $list->idx;
@@ -212,16 +229,12 @@ abstract class Statement
* Whether options were parsed or not.
* For statements that do not have any options this is set to `true` by
* default.
*
* @var bool
*/
$parsedOptions = empty(static::$OPTIONS);
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
*
* @var Token
*/
$token = $list->tokens[$list->idx];
@@ -240,17 +253,17 @@ abstract class Statement
// Only keywords are relevant here. Other parts of the query are
// processed in the functions below.
if ($token->type !== Token::TYPE_KEYWORD) {
if (($token->type !== Token::TYPE_COMMENT)
&& ($token->type !== Token::TYPE_WHITESPACE)
) {
if (($token->type !== Token::TYPE_COMMENT) && ($token->type !== Token::TYPE_WHITESPACE)) {
$parser->error('Unexpected token.', $token);
}
continue;
}
// Unions are parsed by the parser because they represent more than
// one statement.
if (($token->keyword === 'UNION') ||
if (
($token->keyword === 'UNION') ||
($token->keyword === 'UNION ALL') ||
($token->keyword === 'UNION DISTINCT') ||
($token->keyword === 'EXCEPT') ||
@@ -264,9 +277,7 @@ abstract class Statement
// ON DUPLICATE KEY UPDATE ...
// has to be parsed in parent statement (INSERT or REPLACE)
// so look for it and break
if ($this instanceof Statements\SelectStatement
&& $token->value === 'ON'
) {
if ($this instanceof Statements\SelectStatement && $token->value === 'ON') {
++$list->idx; // Skip ON
// look for ON DUPLICATE KEY UPDATE
@@ -274,7 +285,8 @@ abstract class Statement
$second = $list->getNextOfType(Token::TYPE_KEYWORD);
$third = $list->getNextOfType(Token::TYPE_KEYWORD);
if ($first && $second && $third
if (
$first && $second && $third
&& $first->value === 'DUPLICATE'
&& $second->value === 'KEY'
&& $third->value === 'UPDATE'
@@ -283,6 +295,7 @@ abstract class Statement
break;
}
}
$list->idx = $lastIdx;
/**
@@ -301,22 +314,19 @@ abstract class Statement
/**
* Parser's options.
*
* @var array
*/
$options = array();
$options = [];
// Looking for duplicated clauses.
if (! empty(Parser::$KEYWORD_PARSERS[$token->value])
if (
! empty(Parser::$KEYWORD_PARSERS[$token->value])
|| ! empty(Parser::$STATEMENT_PARSERS[$token->value])
) {
if (! empty($parsedClauses[$token->value])) {
$parser->error(
'This type of clause was previously parsed.',
$token
);
$parser->error('This type of clause was previously parsed.', $token);
break;
}
$parsedClauses[$token->value] = true;
}
@@ -324,18 +334,19 @@ abstract class Statement
// Fix Issue #221: As `truncate` is not a keyword
// but it might be the beginning of a statement of truncate,
// so let the value use the keyword field for truncate type.
$token_value = in_array($token->keyword, array('TRUNCATE')) ? $token->keyword : $token->value;
if (! empty(Parser::$KEYWORD_PARSERS[$token_value]) && $list->idx < $list->count) {
$class = Parser::$KEYWORD_PARSERS[$token_value]['class'];
$field = Parser::$KEYWORD_PARSERS[$token_value]['field'];
if (! empty(Parser::$KEYWORD_PARSERS[$token_value]['options'])) {
$options = Parser::$KEYWORD_PARSERS[$token_value]['options'];
$tokenValue = in_array($token->keyword, ['TRUNCATE']) ? $token->keyword : $token->value;
if (! empty(Parser::$KEYWORD_PARSERS[$tokenValue]) && $list->idx < $list->count) {
$class = Parser::$KEYWORD_PARSERS[$tokenValue]['class'];
$field = Parser::$KEYWORD_PARSERS[$tokenValue]['field'];
if (! empty(Parser::$KEYWORD_PARSERS[$tokenValue]['options'])) {
$options = Parser::$KEYWORD_PARSERS[$tokenValue]['options'];
}
}
// Checking if this is the beginning of the statement.
if (! empty(Parser::$STATEMENT_PARSERS[$token->keyword])) {
if (! empty(static::$CLAUSES) // Undefined for some statements.
if (
! empty(static::$CLAUSES) // Undefined for some statements.
&& empty(static::$CLAUSES[$token->value])
) {
// Some keywords (e.g. `SET`) may be the beginning of a
@@ -349,41 +360,37 @@ abstract class Statement
);
break;
}
if (! $parsedOptions) {
if (empty(static::$OPTIONS[$token->value])) {
// Skipping keyword because if it is not a option.
++$list->idx;
}
$this->options = OptionsArray::parse(
$parser,
$list,
static::$OPTIONS
);
$this->options = OptionsArray::parse($parser, $list, static::$OPTIONS);
$parsedOptions = true;
}
} elseif ($class === null) {
if ($this instanceof Statements\SelectStatement
if ($this instanceof Statements\SelectStatement && $token->value === 'WITH ROLLUP') {
// Handle group options in Select statement
// See Statements\SelectStatement::$GROUP_OPTIONS
$this->group_options = OptionsArray::parse($parser, $list, static::$GROUP_OPTIONS);
} elseif (
$this instanceof Statements\SelectStatement
&& ($token->value === 'FOR UPDATE'
|| $token->value === 'LOCK IN SHARE MODE')
) {
// Handle special end options in Select statement
// See Statements\SelectStatement::$END_OPTIONS
$this->end_options = OptionsArray::parse(
$parser,
$list,
static::$END_OPTIONS
);
} elseif ($this instanceof Statements\SetStatement
$this->end_options = OptionsArray::parse($parser, $list, static::$END_OPTIONS);
} elseif (
$this instanceof Statements\SetStatement
&& ($token->value === 'COLLATE'
|| $token->value === 'DEFAULT')
) {
// Handle special end options in SET statement
// See Statements\SetStatement::$END_OPTIONS
$this->end_options = OptionsArray::parse(
$parser,
$list,
static::$END_OPTIONS
);
$this->end_options = OptionsArray::parse($parser, $list, static::$END_OPTIONS);
} else {
// There is no parser for this keyword and isn't the beginning
// of a statement (so no options) either.
@@ -401,19 +408,12 @@ abstract class Statement
$parser->error('Keyword at end of statement.', $token);
continue;
}
++$list->idx; // Skipping keyword or last option.
$this->$field = $class::parse($parser, $list, $options);
}
$this->after($parser, $list, $token);
// #223 Here may make a patch, if last is delimiter, back one
// TODO: when not supporting PHP 5.3 anymore, replace this by FunctionCall::class.
if ($class === 'PhpMyAdmin\\SqlParser\\Components\\FunctionCall'
&& $list->offsetGet($list->idx)->type === Token::TYPE_DELIMITER
) {
--$list->idx;
}
}
// This may be corrected by the parser.
@@ -426,6 +426,8 @@ abstract class Statement
* @param Parser $parser the instance that requests parsing
* @param TokensList $list the list of tokens to be parsed
* @param Token $token the token that is being parsed
*
* @return void
*/
public function before(Parser $parser, TokensList $list, Token $token)
{
@@ -437,6 +439,8 @@ abstract class Statement
* @param Parser $parser the instance that requests parsing
* @param TokensList $list the list of tokens to be parsed
* @param Token $token the token that is being parsed
*
* @return void
*/
public function after(Parser $parser, TokensList $list, Token $token)
{
@@ -445,7 +449,8 @@ abstract class Statement
/**
* Gets the clauses of this statement.
*
* @return array
* @return array<string, array<int, int|string>>
* @psalm-return array<string, array{non-empty-string, (1|2|3)}>
*/
public function getClauses()
{
@@ -473,6 +478,7 @@ abstract class Statement
* @param TokensList $list the list of tokens to be parsed
*
* @return bool
*
* @throws Exceptions\ParserException
*/
public function validateClauseOrder($parser, $list)
@@ -508,13 +514,10 @@ abstract class Statement
$error = 0;
$lastIdx = 0;
foreach ($clauses as $clauseType => $index) {
$clauseStartIdx = Utils\Query::getClauseStartOffset(
$this,
$list,
$clauseType
);
$clauseStartIdx = Utils\Query::getClauseStartOffset($this, $list, $clauseType);
if ($clauseStartIdx !== -1
if (
$clauseStartIdx !== -1
&& $this instanceof Statements\SelectStatement
&& ($clauseType === 'FORCE'
|| $clauseType === 'IGNORE'
@@ -541,19 +544,17 @@ abstract class Statement
if ($clauseStartIdx !== -1 && $clauseStartIdx < $minIdx) {
if ($minJoin === 0 || $error === 1) {
$token = $list->tokens[$clauseStartIdx];
$parser->error(
'Unexpected ordering of clauses.',
$token
);
$parser->error('Unexpected ordering of clauses.', $token);
return false;
}
$minIdx = $clauseStartIdx;
} elseif ($clauseStartIdx !== -1) {
$minIdx = $clauseStartIdx;
}
$lastIdx = ($clauseStartIdx !== -1) ? $clauseStartIdx : $lastIdx;
$lastIdx = $clauseStartIdx !== -1 ? $clauseStartIdx : $lastIdx;
}
return true;