diff --git a/js/codemirror/addon/lint/sql-lint.js b/js/codemirror/addon/lint/sql-lint.js index 557d3a478c..6bc206a4d6 100644 --- a/js/codemirror/addon/lint/sql-lint.js +++ b/js/codemirror/addon/lint/sql-lint.js @@ -30,7 +30,8 @@ CodeMirror.sqlLint = function(text, updateLinting, options, cm) { method: "POST", url: "lint.php", data: { - 'sql_query': text + sql_query: text, + token: PMA_commonParams.get('token'), }, success: handleResponse }); diff --git a/libraries/Linter.class.php b/libraries/Linter.class.php index cfc534acfd..a8ea1a2f5a 100644 --- a/libraries/Linter.class.php +++ b/libraries/Linter.class.php @@ -78,19 +78,16 @@ class PMA_Linter { // Disabling lint for huge queries to save some resources. if (/*overload*/mb_strlen($query) > 10000) { - echo json_encode( + return array( array( - array( - 'message' => 'Linting is disabled for this query because it exceeds the maximum length.', - 'fromLine' => 0, - 'fromColumn' => 0, - 'toLine' => 0, - 'toColumn' => 0, - 'severity' => 'warning', - ) + 'message' => __('Linting is disabled for this query because it exceeds the maximum length.'), + 'fromLine' => 0, + 'fromColumn' => 0, + 'toLine' => 0, + 'toColumn' => 0, + 'severity' => 'warning', ) ); - return; } /** @@ -146,7 +143,10 @@ class PMA_Linter // Building the response. $response[] = array( - 'message' => $error[0] . ' (near ' . $error[2] . ')', + 'message' => sprintf( + __('%1$s (near %2$s)'), + $error[0], $error[2] + ), 'fromLine' => $fromLine, 'fromColumn' => $fromColumn, 'toLine' => $toLine, @@ -156,7 +156,7 @@ class PMA_Linter } // Sending back the answer. - echo json_encode($response); + return $response; } } diff --git a/libraries/config/messages.inc.php b/libraries/config/messages.inc.php index 4b684fdb81..758d772435 100644 --- a/libraries/config/messages.inc.php +++ b/libraries/config/messages.inc.php @@ -58,7 +58,8 @@ $strConfigCodemirrorEnable_desc = __( ); $strConfigCodemirrorEnable_name = __('Enable CodeMirror'); $strConfigLintEnable_desc = __('Find any errors in the query before executing it.' - . ' Requires CodeMirror to be enabled.'); + . ' Requires CodeMirror to be enabled.' +); $strConfigLintEnable_name = __('Enable linter'); $strConfigMinSizeForInputField_desc = __( 'Defines the minimum size for input fields generated for CHAR and VARCHAR ' diff --git a/libraries/sql-parser/src/Component.php b/libraries/sql-parser/src/Component.php index 8c6107f7b1..296ce77ade 100644 --- a/libraries/sql-parser/src/Component.php +++ b/libraries/sql-parser/src/Component.php @@ -35,9 +35,7 @@ abstract class Component * @return mixed */ public static function parse( - Parser $parser, - TokensList $list, - array $options = array() + Parser $parser, TokensList $list, array $options = array() ) { // This method should be abstract, but it can't be both static and // abstract. diff --git a/libraries/sql-parser/src/Components/AlterOperation.php b/libraries/sql-parser/src/Components/AlterOperation.php index 9f859e2787..9cae43c800 100644 --- a/libraries/sql-parser/src/Components/AlterOperation.php +++ b/libraries/sql-parser/src/Components/AlterOperation.php @@ -187,7 +187,10 @@ class AlterOperation extends Component } if ($ret->options->isEmpty()) { - $parser->error('Unrecognized alter operation.', $list->tokens[$list->idx]); + $parser->error( + __('Unrecognized alter operation.'), + $list->tokens[$list->idx] + ); return null; } diff --git a/libraries/sql-parser/src/Components/Array2d.php b/libraries/sql-parser/src/Components/Array2d.php index 64dea40de8..c53c8be9e6 100644 --- a/libraries/sql-parser/src/Components/Array2d.php +++ b/libraries/sql-parser/src/Components/Array2d.php @@ -85,7 +85,14 @@ class Array2d extends Component if ($count === -1) { $count = $arrCount; } elseif ($arrCount != $count) { - $parser->error("{$count} values were expected, but found {$arrCount}.", $token); + $parser->error( + sprintf( + __('%1$d values were expected, but found %2$d.'), + $count, + $arrCount + ), + $token + ); } $ret[] = $arr; $state = 1; @@ -103,7 +110,7 @@ class Array2d extends Component if ($state === 0) { $parser->error( - 'An opening bracket followed by a set of values was expected.', + __('An opening bracket followed by a set of values was expected.'), $list->tokens[$list->idx] ); } diff --git a/libraries/sql-parser/src/Components/ArrayObj.php b/libraries/sql-parser/src/Components/ArrayObj.php index d38077c88d..8dde51857a 100644 --- a/libraries/sql-parser/src/Components/ArrayObj.php +++ b/libraries/sql-parser/src/Components/ArrayObj.php @@ -97,7 +97,10 @@ class ArrayObj extends Component if ($state === 0) { if (($token->type !== Token::TYPE_OPERATOR) || ($token->value !== '(')) { - $parser->error('An opening bracket was expected.', $token); + $parser->error( + __('An opening bracket was expected.'), + $token + ); break; } $state = 1; @@ -111,7 +114,10 @@ class ArrayObj extends Component $state = 2; } elseif ($state === 2) { if (($token->type !== Token::TYPE_OPERATOR) || (($token->value !== ',') && ($token->value !== ')'))) { - $parser->error('A comma or a closing bracket was expected', $token); + $parser->error( + __('A comma or a closing bracket was expected'), + $token + ); break; } if ($token->value === ',') { diff --git a/libraries/sql-parser/src/Components/DataType.php b/libraries/sql-parser/src/Components/DataType.php index e541aaaabc..76c9c29b70 100644 --- a/libraries/sql-parser/src/Components/DataType.php +++ b/libraries/sql-parser/src/Components/DataType.php @@ -122,7 +122,7 @@ class DataType extends Component if ($state === 0) { $ret->name = strtoupper($token->value); if (($token->type !== Token::TYPE_KEYWORD) || (!($token->flags & Token::FLAG_KEYWORD_DATA_TYPE))) { - $parser->error('Unrecognized data type.', $token); + $parser->error(__('Unrecognized data type.'), $token); } $state = 1; } elseif ($state === 1) { diff --git a/libraries/sql-parser/src/Components/Expression.php b/libraries/sql-parser/src/Components/Expression.php index 45413bebb8..b9f257c7ed 100644 --- a/libraries/sql-parser/src/Components/Expression.php +++ b/libraries/sql-parser/src/Components/Expression.php @@ -219,7 +219,7 @@ class Expression extends Component break; } } elseif ($brackets < 0) { - $parser->error('Unexpected closing bracket.', $token); + $parser->error(__('Unexpected closing bracket.'), $token); $brackets = 0; } } elseif ($token->value === ',') { @@ -240,7 +240,7 @@ class Expression extends Component if ($alias) { // An alias is expected (the keyword `AS` was previously found). if (!empty($ret->alias)) { - $parser->error('An alias was previously found.', $token); + $parser->error(__('An alias was previously found.'), $token); } $ret->alias = $token->value; $alias = 0; @@ -251,7 +251,7 @@ class Expression extends Component // the column name we parsed is actually the table name // and the table name is actually a database name. if ((!empty($ret->database)) || ($dot)) { - $parser->error('Unexpected dot.', $token); + $parser->error(__('Unexpected dot.'), $token); } $ret->database = $ret->table; $ret->table = $ret->column; @@ -277,7 +277,8 @@ class Expression extends Component ) { if (!empty($ret->alias)) { $parser->error( - 'An alias was previously found.', $token + __('An alias was previously found.'), + $token ); } $ret->alias = $token->value; @@ -296,7 +297,8 @@ class Expression extends Component ) { if (!empty($ret->alias)) { $parser->error( - 'An alias was previously found.', $token + __('An alias was previously found.'), + $token ); } $ret->alias = $token->value; @@ -318,7 +320,10 @@ class Expression extends Component } if ($alias === 2) { - $parser->error('An alias was expected.', $list->tokens[$list->idx - 1]); + $parser->error( + __('An alias was expected.'), + $list->tokens[$list->idx - 1] + ); } // Whitespaces might be added at the end. diff --git a/libraries/sql-parser/src/Components/ExpressionArray.php b/libraries/sql-parser/src/Components/ExpressionArray.php index afabc3acc8..e006aafeda 100644 --- a/libraries/sql-parser/src/Components/ExpressionArray.php +++ b/libraries/sql-parser/src/Components/ExpressionArray.php @@ -36,8 +36,6 @@ class ExpressionArray extends Component { $ret = array(); - $expr = null; - /** * The state of the parser. * @@ -92,7 +90,7 @@ class ExpressionArray extends Component if ($state === 0) { $parser->error( - 'An expression was expected.', + __('An expression was expected.'), $list->tokens[$list->idx] ); } diff --git a/libraries/sql-parser/src/Components/FieldDefinition.php b/libraries/sql-parser/src/Components/FieldDefinition.php index 4df98cb2a5..32a426ba00 100644 --- a/libraries/sql-parser/src/Components/FieldDefinition.php +++ b/libraries/sql-parser/src/Components/FieldDefinition.php @@ -198,7 +198,10 @@ class FieldDefinition extends Component if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '(')) { $state = 1; } else { - $parser->error('An opening bracket was expected.', $token); + $parser->error( + __('An opening bracket was expected.'), + $token + ); break; } } elseif ($state === 1) { @@ -248,7 +251,10 @@ class FieldDefinition extends Component } if (($state !== 0) && ($state !== 6)) { - $parser->error('A closing bracket was expected.', $list->tokens[$list->idx - 1]); + $parser->error( + __('A closing bracket was expected.'), + $list->tokens[$list->idx - 1] + ); } --$list->idx; diff --git a/libraries/sql-parser/src/Components/Limit.php b/libraries/sql-parser/src/Components/Limit.php index 86ab55afb7..a821b9fe79 100644 --- a/libraries/sql-parser/src/Components/Limit.php +++ b/libraries/sql-parser/src/Components/Limit.php @@ -87,7 +87,7 @@ class Limit extends Component if (($token->type === Token::TYPE_KEYWORD) && ($token->value === 'OFFSET')) { if ($offset) { - $parser->error('An offset was expected.', $token); + $parser->error(__('An offset was expected.'), $token); } $offset = true; continue; @@ -108,7 +108,10 @@ class Limit extends Component } if ($offset) { - $parser->error('An offset was expected.', $list->tokens[$list->idx - 1]); + $parser->error( + __('An offset was expected.'), + $list->tokens[$list->idx - 1] + ); } --$list->idx; diff --git a/libraries/sql-parser/src/Components/OptionsArray.php b/libraries/sql-parser/src/Components/OptionsArray.php index 487eefd4d2..0e863266db 100644 --- a/libraries/sql-parser/src/Components/OptionsArray.php +++ b/libraries/sql-parser/src/Components/OptionsArray.php @@ -137,7 +137,13 @@ class OptionsArray extends Component // real options (e.g. if there are 5 options, the first // fake ID is 6). if (isset($ret->options[$lastOptionId])) { - $parser->error('This option conflicts with \'' . $ret->options[$lastOptionId] . '\'.', $token); + $parser->error( + sprintf( + __('This option conflicts with "%1$s".'), + $ret->options[$lastOptionId] + ), + $token + ); $lastOptionId = $lastAssignedId++; } } else { diff --git a/libraries/sql-parser/src/Components/RenameOperation.php b/libraries/sql-parser/src/Components/RenameOperation.php index 6bd9bbb0b7..7d115fb330 100644 --- a/libraries/sql-parser/src/Components/RenameOperation.php +++ b/libraries/sql-parser/src/Components/RenameOperation.php @@ -98,14 +98,20 @@ class RenameOperation extends Component ) ); if (empty($expr->old)) { - $parser->error('The old name of the table was expected.', $token); + $parser->error( + __('The old name of the table was expected.'), + $token + ); } $state = 1; } elseif ($state === 1) { if (($token->type === Token::TYPE_KEYWORD) && ($token->value === 'TO')) { $state = 2; } else { - $parser->error('Keyword "TO" was expected.', $token); + $parser->error( + __('Keyword "TO" was expected.'), + $token + ); break; } } elseif ($state === 2) { @@ -119,7 +125,10 @@ class RenameOperation extends Component ) ); if (empty($expr->new)) { - $parser->error('The new name of the table was expected.', $token); + $parser->error( + __('The new name of the table was expected.'), + $token + ); } $state = 3; } elseif ($state === 3) { @@ -134,7 +143,10 @@ class RenameOperation extends Component } if ($state !== 3) { - $parser->error('A rename operation was expected.', $list->tokens[$list->idx - 1]); + $parser->error( + __('A rename operation was expected.'), + $list->tokens[$list->idx - 1] + ); } // Last iteration was not saved. diff --git a/libraries/sql-parser/src/Lexer.php b/libraries/sql-parser/src/Lexer.php index aae95f1a58..96be968a46 100644 --- a/libraries/sql-parser/src/Lexer.php +++ b/libraries/sql-parser/src/Lexer.php @@ -9,48 +9,67 @@ * * @package SqlParser */ -namespace SqlParser; -use SqlParser\Exceptions\LexerException; +namespace { -if (!defined('USE_UTF_STRINGS')) { + if (!function_exists('__')) { + + /** + * Translates the given string. + * + * @param string $str String to be translated. + * + * @return string + */ + function __($str) + { + return $str; + } + } +} + +namespace SqlParser { + + use SqlParser\Exceptions\LexerException; + + if (!defined('USE_UTF_STRINGS')) { + + /** + * Forces usage of `UtfString` if the string is multibyte. + * `UtfString` may be slower, but it gives better results. + * @var bool + */ + define('USE_UTF_STRINGS', true); + } + + // Set internal character to UTF-8. + // In previous versions of PHP (5.5 and older) the default internal encoding is + // "ISO-8859-1". + if ((defined('USE_UTF_STRINGS')) && (USE_UTF_STRINGS)) { + mb_internal_encoding('UTF-8'); + } /** - * Forces usage of `UtfString` if the string is multibyte. - * `UtfString` may be slower, but it gives better results. - * @var bool + * Performs lexical analysis over a SQL statement and splits it in multiple + * tokens. + * + * The output of the lexer is affected by the context of the SQL statement. + * + * @category Lexer + * @package SqlParser + * @author Dan Ungureanu + * @license http://opensource.org/licenses/GPL-2.0 GNU Public License + * @see Context */ - define('USE_UTF_STRINGS', true); -} + class Lexer + { -// Set internal character to UTF-8. -// In previous versions of PHP (5.5 and older) the default internal encoding is -// "ISO-8859-1". -if ((defined('USE_UTF_STRINGS')) && (USE_UTF_STRINGS)) { - mb_internal_encoding('UTF-8'); -} - -/** - * Performs lexical analysis over a SQL statement and splits it in multiple - * tokens. - * - * The output of the lexer is affected by the context of the SQL statement. - * - * @category Lexer - * @package SqlParser - * @author Dan Ungureanu - * @license http://opensource.org/licenses/GPL-2.0 GNU Public License - * @see Context - */ -class Lexer -{ - - /** + /** * A list of methods that are used in lexing the SQL query. * * @var array */ - public static $PARSER_METHODS = array( + public static $PARSER_METHODS = array( // It is best to put the parsers in order of their complexity // (ascending) and their occurrence rate (descending). @@ -77,654 +96,678 @@ class Lexer 'parseDelimiter', 'parseWhitespace', 'parseNumber', 'parseComment', 'parseOperator', 'parseBool', 'parseString', 'parseSymbol', 'parseKeyword', 'parseUnknown' - ); - - /** - * Whether errors should throw exceptions or just be stored. - * - * @var bool - * - * @see static::$errors - */ - public $strict = false; - - /** - * The string to be parsed. - * - * @var string|UtfString - */ - public $str = ''; - - /** - * The length of `$str`. - * - * By storing its length, a lot of time is saved, because parsing methods - * would call `strlen` everytime. - * - * @var int - */ - public $len = 0; - - /** - * The index of the last parsed character. - * - * @var int - */ - public $last = 0; - - /** - * Tokens extracted from given strings. - * - * @var TokensList - */ - public $list; - - /** - * The default delimiter. This is used, by default, in all new instances. - * - * @var string - */ - public static $DEFAULT_DELIMITER = ';'; - - /** - * Statements delimiter. - * This may change during lexing. - * - * @var string - */ - public $delimiter = ';'; - - /** - * The length of the delimiter. - * - * Because `parseDelimiter` can be called a lot, it would perform a lot of - * calls to `strlen`, which might affect performance when the delimiter is - * big. - * - * @var int - */ - public $delimiterLen = 1; - - /** - * List of errors that occurred during lexing. - * - * Usually, the lexing does not stop once an error occurred because that - * error might be false positive or a partial result (even a bad one) - * might be needed. - * - * @var LexerException[] - * - * @see Lexer::error() - */ - public $errors = array(); - - /** - * Constructor. - * - * @param string|UtfString $str The query to be lexed. - * @param bool $strict Whether strict mode should be enabled or not. - */ - public function __construct($str, $strict = false) - { - // `strlen` is used instead of `mb_strlen` because the lexer needs to - // parse each byte of the input. - $len = ($str instanceof UtfString) ? $str->length() : strlen($str); - - // For multi-byte strings, a new instance of `UtfString` is - // initialized (only if `UtfString` usage is forced. - if (!($str instanceof UtfString)) { - if ((USE_UTF_STRINGS) && ($len != mb_strlen($str))) { - $str = new UtfString($str); - } - } - - $this->str = $str; - $this->len = ($str instanceof UtfString) ? $str->length() : $len; - - $this->strict = $strict; - - // Setting the delimiter. - $this->delimiter = static::$DEFAULT_DELIMITER; - - $this->lex(); - } - - /** - * Parses the string and extracts lexemes. - * - * @return void - */ - public function lex() - { - // TODO: Sometimes, static::parse* functions make unnecessary calls to - // is* functions. For a better performance, some rules can be deduced - // from context. - // For example, in `parseBool` there is no need to compare the token - // every time with `true` and `false`. The first step would be to - // compare with 'true' only and just after that add another letter from - // context and compare again with `false`. - // Another example is `parseComment`. - - $list = new TokensList(); + ); /** - * Last processed token. - * @var Token $lastToken + * Whether errors should throw exceptions or just be stored. + * + * @var bool + * + * @see static::$errors */ - $lastToken = null; + public $strict = false; + + /** + * The string to be parsed. + * + * @var string|UtfString + */ + public $str = ''; + + /** + * The length of `$str`. + * + * By storing its length, a lot of time is saved, because parsing methods + * would call `strlen` everytime. + * + * @var int + */ + public $len = 0; + + /** + * The index of the last parsed character. + * + * @var int + */ + public $last = 0; + + /** + * Tokens extracted from given strings. + * + * @var TokensList + */ + public $list; + + /** + * The default delimiter. This is used, by default, in all new instances. + * + * @var string + */ + public static $DEFAULT_DELIMITER = ';'; + + /** + * Statements delimiter. + * This may change during lexing. + * + * @var string + */ + public $delimiter = ';'; + + /** + * The length of the delimiter. + * + * Because `parseDelimiter` can be called a lot, it would perform a lot of + * calls to `strlen`, which might affect performance when the delimiter is + * big. + * + * @var int + */ + public $delimiterLen = 1; + + /** + * List of errors that occurred during lexing. + * + * Usually, the lexing does not stop once an error occurred because that + * error might be false positive or a partial result (even a bad one) + * might be needed. + * + * @var LexerException[] + * + * @see Lexer::error() + */ + public $errors = array(); + + /** + * Constructor. + * + * @param string|UtfString $str The query to be lexed. + * @param bool $strict Whether strict mode should be enabled or not. + */ + public function __construct($str, $strict = false) + { + // `strlen` is used instead of `mb_strlen` because the lexer needs to + // parse each byte of the input. + $len = ($str instanceof UtfString) ? $str->length() : strlen($str); + + // For multi-byte strings, a new instance of `UtfString` is + // initialized (only if `UtfString` usage is forced. + if (!($str instanceof UtfString)) { + if ((USE_UTF_STRINGS) && ($len != mb_strlen($str))) { + $str = new UtfString($str); + } + } + + $this->str = $str; + $this->len = ($str instanceof UtfString) ? $str->length() : $len; + + $this->strict = $strict; + + // Setting the delimiter. + $this->delimiter = static::$DEFAULT_DELIMITER; + + $this->lex(); + } + + /** + * Parses the string and extracts lexemes. + * + * @return void + */ + public function lex() + { + // TODO: Sometimes, static::parse* functions make unnecessary calls to + // is* functions. For a better performance, some rules can be deduced + // from context. + // For example, in `parseBool` there is no need to compare the token + // every time with `true` and `false`. The first step would be to + // compare with 'true' only and just after that add another letter from + // context and compare again with `false`. + // Another example is `parseComment`. + + $list = new TokensList(); - for ($this->last = 0, $lastIdx = 0; $this->last < $this->len; $lastIdx = ++$this->last) { /** - * The new token. - * @var Token $token + * Last processed token. + * @var Token $lastToken */ - $token = null; + $lastToken = null; - foreach (static::$PARSER_METHODS as $method) { - if (($token = $this->$method())) { - break; + for ($this->last = 0, $lastIdx = 0; $this->last < $this->len; $lastIdx = ++$this->last) { + /** + * The new token. + * @var Token $token + */ + $token = null; + + foreach (static::$PARSER_METHODS as $method) { + if (($token = $this->$method())) { + break; + } } - } - if ($token === null) { - // @assert($this->last === $lastIdx); - $token = new Token($this->str[$this->last]); - $this->error('Unexpected character.', $this->str[$this->last], $this->last); - } elseif (($token->type === Token::TYPE_SYMBOL) - && ($token->flags & Token::FLAG_SYMBOL_VARIABLE) - && ($lastToken !== null) - ) { - // Handles ```... FROM 'user'@'%' ...```. - if ((($lastToken->type === Token::TYPE_SYMBOL) - && ($lastToken->flags & Token::FLAG_SYMBOL_BACKTICK)) - || ($lastToken->type === Token::TYPE_STRING) + if ($token === null) { + // @assert($this->last === $lastIdx); + $token = new Token($this->str[$this->last]); + $this->error( + __('Unexpected character.'), + $this->str[$this->last], + $this->last + ); + } elseif (($token->type === Token::TYPE_SYMBOL) + && ($token->flags & Token::FLAG_SYMBOL_VARIABLE) + && ($lastToken !== null) ) { - $lastToken->token .= $token->token; - $lastToken->type = Token::TYPE_SYMBOL; - $lastToken->flags = Token::FLAG_SYMBOL_USER; - $lastToken->value .= '@' . $token->value; - continue; + // Handles ```... FROM 'user'@'%' ...```. + if ((($lastToken->type === Token::TYPE_SYMBOL) + && ($lastToken->flags & Token::FLAG_SYMBOL_BACKTICK)) + || ($lastToken->type === Token::TYPE_STRING) + ) { + $lastToken->token .= $token->token; + $lastToken->type = Token::TYPE_SYMBOL; + $lastToken->flags = Token::FLAG_SYMBOL_USER; + $lastToken->value .= '@' . $token->value; + continue; + } } - } - $token->position = $lastIdx; + $token->position = $lastIdx; - $list->tokens[$list->count++] = $token; + $list->tokens[$list->count++] = $token; - // Handling delimiters. - if (($token->type === Token::TYPE_NONE) && ($token->value === 'DELIMITER')) { - if ($this->last + 1 >= $this->len) { - $this->error('Expected whitespace(s) before delimiter.', '', $this->last + 1); - continue; - } + // Handling delimiters. + if (($token->type === Token::TYPE_NONE) && ($token->value === 'DELIMITER')) { + if ($this->last + 1 >= $this->len) { + $this->error( + __('Expected whitespace(s) before delimiter.'), + '', + $this->last + 1 + ); + continue; + } - // Skipping last R (from `delimiteR`) and whitespaces between - // the keyword `DELIMITER` and the actual delimiter. - $pos = ++$this->last; - if (($token = $this->parseWhitespace()) !== null) { + // Skipping last R (from `delimiteR`) and whitespaces between + // the keyword `DELIMITER` and the actual delimiter. + $pos = ++$this->last; + if (($token = $this->parseWhitespace()) !== null) { + $token->position = $pos; + $list->tokens[$list->count++] = $token; + } + + // Preparing the token that holds the new delimiter. + if ($this->last + 1 >= $this->len) { + $this->error( + __('Expected delimiter.'), + '', + $this->last + 1 + ); + continue; + } + $pos = $this->last + 1; + + // Parsing the delimiter. + $this->delimiter = ''; + while ((++$this->last < $this->len) && (!Context::isWhitespace($this->str[$this->last]))) { + $this->delimiter .= $this->str[$this->last]; + } + --$this->last; + + // Saving the delimiter and its token. + $this->delimiterLen = strlen($this->delimiter); + $token = new Token($this->delimiter, Token::TYPE_DELIMITER); $token->position = $pos; $list->tokens[$list->count++] = $token; } - // Preparing the token that holds the new delimiter. - if ($this->last + 1 >= $this->len) { - $this->error('Expected delimiter.', '', $this->last + 1); - continue; - } - $pos = $this->last + 1; - - // Parsing the delimiter. - $this->delimiter = ''; - while ((++$this->last < $this->len) && (!Context::isWhitespace($this->str[$this->last]))) { - $this->delimiter .= $this->str[$this->last]; - } - --$this->last; - - // Saving the delimiter and its token. - $this->delimiterLen = strlen($this->delimiter); - $token = new Token($this->delimiter, Token::TYPE_DELIMITER); - $token->position = $pos; - $list->tokens[$list->count++] = $token; + $lastToken = $token; } - $lastToken = $token; + // Adding a final delimiter to mark the ending. + $list->tokens[$list->count++] = new Token(null, Token::TYPE_DELIMITER); + + // Saving the tokens list. + $this->list = $list; } - // Adding a final delimiter to mark the ending. - $list->tokens[$list->count++] = new Token(null, Token::TYPE_DELIMITER); - - // Saving the tokens list. - $this->list = $list; - } - - /** - * Creates a new error log. - * - * @param string $msg The error message. - * @param string $str The character that produced the error. - * @param int $pos The position of the character. - * @param int $code The code of the error. - * - * @throws LexerException Throws the exception, if strict mode is enabled. - * - * @return void - */ - public function error($msg = '', $str = '', $pos = 0, $code = 0) - { - $error = new LexerException($msg, $str, $pos, $code); - if ($this->strict) { - throw $error; + /** + * Creates a new error log. + * + * @param string $msg The error message. + * @param string $str The character that produced the error. + * @param int $pos The position of the character. + * @param int $code The code of the error. + * + * @throws LexerException Throws the exception, if strict mode is enabled. + * + * @return void + */ + public function error($msg = '', $str = '', $pos = 0, $code = 0) + { + $error = new LexerException($msg, $str, $pos, $code); + if ($this->strict) { + throw $error; + } + $this->errors[] = $error; } - $this->errors[] = $error; - } - - /** - * Parses a keyword. - * - * @return Token - */ - public function parseKeyword() - { - $token = ''; /** - * Value to be returned. - * @var Token $ret + * Parses a keyword. + * + * @return Token */ - $ret = null; + public function parseKeyword() + { + $token = ''; - /** - * The value of `$this->last` where `$token` ends in `$this->str`. - * @var int $iEnd - */ - $iEnd = $this->last; + /** + * Value to be returned. + * @var Token $ret + */ + $ret = null; - /** - * Whether last parsed character is a whitespace. - * @var bool $lastSpace - */ - $lastSpace = false; + /** + * The value of `$this->last` where `$token` ends in `$this->str`. + * @var int $iEnd + */ + $iEnd = $this->last; - for ($j = 1; $j < Context::KEYWORD_MAX_LENGTH && $this->last < $this->len; ++$j, ++$this->last) { - // Composed keywords shouldn't have more than one whitespace between - // keywords. - if (Context::isWhitespace($this->str[$this->last])) { - if ($lastSpace) { - --$j; // The size of the keyword didn't increase. - continue; + /** + * Whether last parsed character is a whitespace. + * @var bool $lastSpace + */ + $lastSpace = false; + + for ($j = 1; $j < Context::KEYWORD_MAX_LENGTH && $this->last < $this->len; ++$j, ++$this->last) { + // Composed keywords shouldn't have more than one whitespace between + // keywords. + if (Context::isWhitespace($this->str[$this->last])) { + if ($lastSpace) { + --$j; // The size of the keyword didn't increase. + continue; + } else { + $lastSpace = true; + } } else { - $lastSpace = true; + $lastSpace = false; + } + $token .= $this->str[$this->last]; + if (($this->last + 1 === $this->len) || (Context::isSeparator($this->str[$this->last + 1]))) { + if (($flags = Context::isKeyword($token))) { + $ret = new Token($token, Token::TYPE_KEYWORD, $flags); + $iEnd = $this->last; + // We don't break so we find longest keyword. + // For example, `OR` and `ORDER` have a common prefix `OR`. + // If we stopped at `OR`, the parsing would be invalid. + } } - } else { - $lastSpace = false; } - $token .= $this->str[$this->last]; - if (($this->last + 1 === $this->len) || (Context::isSeparator($this->str[$this->last + 1]))) { - if (($flags = Context::isKeyword($token))) { - $ret = new Token($token, Token::TYPE_KEYWORD, $flags); + + $this->last = $iEnd; + return $ret; + } + + /** + * Parses an operator. + * + * @return Token + */ + public function parseOperator() + { + $token = ''; + + /** + * Value to be returned. + * @var Token $ret + */ + $ret = null; + + /** + * The value of `$this->last` where `$token` ends in `$this->str`. + * @var int $iEnd + */ + $iEnd = $this->last; + + for ($j = 1; $j < Context::OPERATOR_MAX_LENGTH && $this->last < $this->len; ++$j, ++$this->last) { + $token .= $this->str[$this->last]; + if ($flags = Context::isOperator($token)) { + $ret = new Token($token, Token::TYPE_OPERATOR, $flags); $iEnd = $this->last; - // We don't break so we find longest keyword. - // For example, `OR` and `ORDER` have a common prefix `OR`. - // If we stopped at `OR`, the parsing would be invalid. } } + + $this->last = $iEnd; + return $ret; } - $this->last = $iEnd; - return $ret; - } - - /** - * Parses an operator. - * - * @return Token - */ - public function parseOperator() - { - $token = ''; - /** - * Value to be returned. - * @var Token $ret + * Parses a whitespace. + * + * @return Token */ - $ret = null; + public function parseWhitespace() + { + $token = $this->str[$this->last]; - /** - * The value of `$this->last` where `$token` ends in `$this->str`. - * @var int $iEnd - */ - $iEnd = $this->last; - - for ($j = 1; $j < Context::OPERATOR_MAX_LENGTH && $this->last < $this->len; ++$j, ++$this->last) { - $token .= $this->str[$this->last]; - if ($flags = Context::isOperator($token)) { - $ret = new Token($token, Token::TYPE_OPERATOR, $flags); - $iEnd = $this->last; + if (!Context::isWhitespace($token)) { + return null; } - } - $this->last = $iEnd; - return $ret; - } - - /** - * Parses a whitespace. - * - * @return Token - */ - public function parseWhitespace() - { - $token = $this->str[$this->last]; - - if (!Context::isWhitespace($token)) { - return null; - } - - while ((++$this->last < $this->len) && (Context::isWhitespace($this->str[$this->last]))) { - $token .= $this->str[$this->last]; - } - - --$this->last; - return new Token($token, Token::TYPE_WHITESPACE); - } - - /** - * Parses a comment. - * - * @return Token - */ - public function parseComment() - { - $iBak = $this->last; - $token = $this->str[$this->last]; - - // Bash style comments. (#comment\n) - if (Context::isComment($token)) { - while ((++$this->last < $this->len) && ($this->str[$this->last] !== "\n")) { + while ((++$this->last < $this->len) && (Context::isWhitespace($this->str[$this->last]))) { $token .= $this->str[$this->last]; } - $token .= $this->str[$this->last]; - return new Token($token, Token::TYPE_COMMENT, Token::FLAG_COMMENT_BASH); + + --$this->last; + return new Token($token, Token::TYPE_WHITESPACE); } - // C style comments. (/*comment*\/) - if (++$this->last < $this->len) { - $token .= $this->str[$this->last]; + /** + * Parses a comment. + * + * @return Token + */ + public function parseComment() + { + $iBak = $this->last; + $token = $this->str[$this->last]; + + // Bash style comments. (#comment\n) if (Context::isComment($token)) { - $flags = Token::FLAG_COMMENT_C; - if (($this->last + 1 < $this->len) && ($this->str[$this->last + 1] === '!')) { - // It is a MySQL-specific command. - $flags |= Token::FLAG_COMMENT_MYSQL_CMD; - } - while ((++$this->last < $this->len) && - (($this->str[$this->last - 1] !== '*') || ($this->str[$this->last] !== '/'))) { + while ((++$this->last < $this->len) && ($this->str[$this->last] !== "\n")) { $token .= $this->str[$this->last]; } $token .= $this->str[$this->last]; - return new Token($token, Token::TYPE_COMMENT, $flags); + return new Token($token, Token::TYPE_COMMENT, Token::FLAG_COMMENT_BASH); } - } - // SQL style comments. (-- comment\n) - if (++$this->last < $this->len) { - $token .= $this->str[$this->last]; - if (Context::isComment($token)) { - if ($this->str[$this->last] !== "\n") { - // Checking if this comment did not end already (```--\n```). - while ((++$this->last < $this->len) && ($this->str[$this->last] !== "\n")) { - $token .= $this->str[$this->last]; - } - if ($this->last < $this->len) { + // C style comments. (/*comment*\/) + if (++$this->last < $this->len) { + $token .= $this->str[$this->last]; + if (Context::isComment($token)) { + $flags = Token::FLAG_COMMENT_C; + if (($this->last + 1 < $this->len) && ($this->str[$this->last + 1] === '!')) { + // It is a MySQL-specific command. + $flags |= Token::FLAG_COMMENT_MYSQL_CMD; + } + while ((++$this->last < $this->len) && + (($this->str[$this->last - 1] !== '*') || ($this->str[$this->last] !== '/'))) { $token .= $this->str[$this->last]; } + $token .= $this->str[$this->last]; + return new Token($token, Token::TYPE_COMMENT, $flags); } - return new Token($token, Token::TYPE_COMMENT, Token::FLAG_COMMENT_SQL); } - } - $this->last = $iBak; - return null; - } + // SQL style comments. (-- comment\n) + if (++$this->last < $this->len) { + $token .= $this->str[$this->last]; + if (Context::isComment($token)) { + if ($this->str[$this->last] !== "\n") { + // Checking if this comment did not end already (```--\n```). + while ((++$this->last < $this->len) && ($this->str[$this->last] !== "\n")) { + $token .= $this->str[$this->last]; + } + if ($this->last < $this->len) { + $token .= $this->str[$this->last]; + } + } + return new Token($token, Token::TYPE_COMMENT, Token::FLAG_COMMENT_SQL); + } + } - /** - * Parses a boolean. - * - * @return Token - */ - public function parseBool() - { - if ($this->last + 3 >= $this->len) { - // At least `min(strlen('TRUE'), strlen('FALSE'))` characters are - // required. + $this->last = $iBak; return null; } - $iBak = $this->last; - $token = $this->str[$this->last] . $this->str[++$this->last] + /** + * Parses a boolean. + * + * @return Token + */ + public function parseBool() + { + if ($this->last + 3 >= $this->len) { + // At least `min(strlen('TRUE'), strlen('FALSE'))` characters are + // required. + return null; + } + + $iBak = $this->last; + $token = $this->str[$this->last] . $this->str[++$this->last] . $this->str[++$this->last] . $this->str[++$this->last]; // _TRUE_ or _FALS_e - if (Context::isBool($token)) { - return new Token($token, Token::TYPE_BOOL); - } elseif (++$this->last < $this->len) { - $token .= $this->str[$this->last]; // fals_E_ if (Context::isBool($token)) { - return new Token($token, Token::TYPE_BOOL, 1); - } - } - - $this->last = $iBak; - return null; - } - - /** - * Parses a number. - * - * @return Token - */ - public function parseNumber() - { - // A rudimentary state machine is being used to parse numbers due to - // the various forms of their notation. - // - // Below are the states of the machines and the conditions to change - // the state. - // - // 1 ---------------------[ + or - ]---------------------> 1 - // 1 --------------------[ 0x or 0X ]--------------------> 2 - // 1 ---------------------[ 0 to 9 ]---------------------> 3 - // 1 ------------------------[ . ]-----------------------> 4 - // - // 2 ---------------------[ 0 to F ]---------------------> 2 - // - // 3 ---------------------[ 0 to 9 ]---------------------> 3 - // 3 ------------------------[ . ]-----------------------> 4 - // 3 ---------------------[ e or E ]---------------------> 5 - // - // 4 ---------------------[ 0 to 9 ]---------------------> 4 - // 4 ---------------------[ e or E ]---------------------> 5 - // - // 5 ----------------[ + or - or 0 to 9 ]----------------> 6 - // - // State 1 may be reached by negative numbers. - // State 2 is reached only by hex numbers. - // State 4 is reached only by float numbers. - // State 5 is reached only by numbers in approximate form. - // - // Valid final states are: 2, 3, 4 and 6. Any parsing that finished in a - // state other than these is invalid. - $iBak = $this->last; - $token = ''; - $flags = 0; - $state = 1; - for (; $this->last < $this->len; ++$this->last) { - if ($state === 1) { - if ($this->str[$this->last] === '-') { - $flags |= Token::FLAG_NUMBER_NEGATIVE; - } elseif (($this->str[$this->last] === '0') && ($this->last + 1 < $this->len) - && (($this->str[$this->last + 1] === 'x') || ($this->str[$this->last + 1] === 'X')) - ) { - $token .= $this->str[$this->last++]; - $state = 2; - } elseif (($this->str[$this->last] >= '0') && ($this->str[$this->last] <= '9')) { - $state = 3; - } elseif ($this->str[$this->last] === '.') { - $state = 4; - } elseif ($this->str[$this->last] !== '+') { - // `+` is a valid character in a number. - break; - } - } elseif ($state === 2) { - $flags |= Token::FLAG_NUMBER_HEX; - if (!((($this->str[$this->last] >= '0') && ($this->str[$this->last] <= '9')) - || (($this->str[$this->last] >= 'A') && ($this->str[$this->last] <= 'F')) - || (($this->str[$this->last] >= 'a') && ($this->str[$this->last] <= 'f'))) - ) { - break; - } - } elseif ($state === 3) { - if ($this->str[$this->last] === '.') { - $state = 4; - } elseif (($this->str[$this->last] === 'e') || ($this->str[$this->last] === 'E')) { - $state = 5; - } elseif (($this->str[$this->last] < '0') || ($this->str[$this->last] > '9')) { - // Just digits and `.`, `e` and `E` are valid characters. - break; - } - } elseif ($state === 4) { - $flags |= Token::FLAG_NUMBER_FLOAT; - if (($this->str[$this->last] === 'e') || ($this->str[$this->last] === 'E')) { - $state = 5; - } elseif (($this->str[$this->last] < '0') || ($this->str[$this->last] > '9')) { - // Just digits, `e` and `E` are valid characters. - break; - } - } elseif ($state === 5) { - $flags |= Token::FLAG_NUMBER_APPROXIMATE; - if (($this->str[$this->last] === '+') || ($this->str[$this->last] === '-') - || ((($this->str[$this->last] >= '0') && ($this->str[$this->last] <= '9'))) - ) { - $state = 6; - } else { - break; - } - } elseif ($state === 6) { - if (($this->str[$this->last] < '0') || ($this->str[$this->last] > '9')) { - // Just digits are valid characters. - break; + return new Token($token, Token::TYPE_BOOL); + } elseif (++$this->last < $this->len) { + $token .= $this->str[$this->last]; // fals_E_ + if (Context::isBool($token)) { + return new Token($token, Token::TYPE_BOOL, 1); } } - $token .= $this->str[$this->last]; - } - if (($state === 2) || ($state === 3) || (($token !== '.') && ($state === 4)) || ($state === 6)) { - --$this->last; - return new Token($token, Token::TYPE_NUMBER, $flags); - } - $this->last = $iBak; - return null; - } - /** - * Parses a string. - * - * @param string $quote Additional starting symbol. - * - * @return Token - */ - public function parseString($quote = '') - { - $token = $this->str[$this->last]; - if ((!($flags = Context::isString($token))) && ($token !== $quote)) { + $this->last = $iBak; return null; } - $quote = $token; - while (++$this->last < $this->len) { - if (($this->last + 1 < $this->len) - && ((($this->str[$this->last] === $quote) && ($this->str[$this->last + 1] === $quote)) - || (($this->str[$this->last] === '\\') && ($quote !== '`'))) - ) { - $token .= $this->str[$this->last] . $this->str[++$this->last]; - } else { - if ($this->str[$this->last] === $quote) { - break; + /** + * Parses a number. + * + * @return Token + */ + public function parseNumber() + { + // A rudimentary state machine is being used to parse numbers due to + // the various forms of their notation. + // + // Below are the states of the machines and the conditions to change + // the state. + // + // 1 ---------------------[ + or - ]---------------------> 1 + // 1 --------------------[ 0x or 0X ]--------------------> 2 + // 1 ---------------------[ 0 to 9 ]---------------------> 3 + // 1 ------------------------[ . ]-----------------------> 4 + // + // 2 ---------------------[ 0 to F ]---------------------> 2 + // + // 3 ---------------------[ 0 to 9 ]---------------------> 3 + // 3 ------------------------[ . ]-----------------------> 4 + // 3 ---------------------[ e or E ]---------------------> 5 + // + // 4 ---------------------[ 0 to 9 ]---------------------> 4 + // 4 ---------------------[ e or E ]---------------------> 5 + // + // 5 ----------------[ + or - or 0 to 9 ]----------------> 6 + // + // State 1 may be reached by negative numbers. + // State 2 is reached only by hex numbers. + // State 4 is reached only by float numbers. + // State 5 is reached only by numbers in approximate form. + // + // Valid final states are: 2, 3, 4 and 6. Any parsing that finished in a + // state other than these is invalid. + $iBak = $this->last; + $token = ''; + $flags = 0; + $state = 1; + for (; $this->last < $this->len; ++$this->last) { + if ($state === 1) { + if ($this->str[$this->last] === '-') { + $flags |= Token::FLAG_NUMBER_NEGATIVE; + } elseif (($this->str[$this->last] === '0') && ($this->last + 1 < $this->len) + && (($this->str[$this->last + 1] === 'x') || ($this->str[$this->last + 1] === 'X')) + ) { + $token .= $this->str[$this->last++]; + $state = 2; + } elseif (($this->str[$this->last] >= '0') && ($this->str[$this->last] <= '9')) { + $state = 3; + } elseif ($this->str[$this->last] === '.') { + $state = 4; + } elseif ($this->str[$this->last] !== '+') { + // `+` is a valid character in a number. + break; + } + } elseif ($state === 2) { + $flags |= Token::FLAG_NUMBER_HEX; + if (!((($this->str[$this->last] >= '0') && ($this->str[$this->last] <= '9')) + || (($this->str[$this->last] >= 'A') && ($this->str[$this->last] <= 'F')) + || (($this->str[$this->last] >= 'a') && ($this->str[$this->last] <= 'f'))) + ) { + break; + } + } elseif ($state === 3) { + if ($this->str[$this->last] === '.') { + $state = 4; + } elseif (($this->str[$this->last] === 'e') || ($this->str[$this->last] === 'E')) { + $state = 5; + } elseif (($this->str[$this->last] < '0') || ($this->str[$this->last] > '9')) { + // Just digits and `.`, `e` and `E` are valid characters. + break; + } + } elseif ($state === 4) { + $flags |= Token::FLAG_NUMBER_FLOAT; + if (($this->str[$this->last] === 'e') || ($this->str[$this->last] === 'E')) { + $state = 5; + } elseif (($this->str[$this->last] < '0') || ($this->str[$this->last] > '9')) { + // Just digits, `e` and `E` are valid characters. + break; + } + } elseif ($state === 5) { + $flags |= Token::FLAG_NUMBER_APPROXIMATE; + if (($this->str[$this->last] === '+') || ($this->str[$this->last] === '-') + || ((($this->str[$this->last] >= '0') && ($this->str[$this->last] <= '9'))) + ) { + $state = 6; + } else { + break; + } + } elseif ($state === 6) { + if (($this->str[$this->last] < '0') || ($this->str[$this->last] > '9')) { + // Just digits are valid characters. + break; + } } $token .= $this->str[$this->last]; } - } - - if (($this->last >= $this->len) || ($this->str[$this->last] !== $quote)) { - $this->error('Ending quote ' . $quote . ' was expected.', '', $this->last); - } else { - $token .= $this->str[$this->last]; - } - return new Token($token, Token::TYPE_STRING, $flags); - } - - /** - * Parses a symbol. - * - * @return Token - */ - public function parseSymbol() - { - $token = $this->str[$this->last]; - if (!($flags = Context::isSymbol($token))) { - return null; - } - - if ($flags & Token::FLAG_SYMBOL_VARIABLE) { - ++$this->last; - } else { - $token = ''; - } - - if (($str = $this->parseString('`')) === null) { - if (($str = static::parseUnknown()) === null) { - $this->error('Variable name was expected.', $this->str[$this->last], $this->last); + if (($state === 2) || ($state === 3) || (($token !== '.') && ($state === 4)) || ($state === 6)) { + --$this->last; + return new Token($token, Token::TYPE_NUMBER, $flags); } - } - - if ($str !== null) { - $token .= $str->token; - } - - return new Token($token, Token::TYPE_SYMBOL, $flags); - } - - /** - * Parses unknown parts of the query. - * - * @return Token - */ - public function parseUnknown() - { - $token = $this->str[$this->last]; - if (Context::isSeparator($token)) { + $this->last = $iBak; return null; } - while ((++$this->last < $this->len) && (!Context::isSeparator($this->str[$this->last]))) { - $token .= $this->str[$this->last]; - } - --$this->last; - return new Token($token); - } - /** - * Parses the delimiter of the query. - * - * @return Token - */ - public function parseDelimiter() - { - $idx = 0; - - while ($idx < $this->delimiterLen) { - if ($this->delimiter[$idx] !== $this->str[$this->last + $idx]) { + /** + * Parses a string. + * + * @param string $quote Additional starting symbol. + * + * @return Token + */ + public function parseString($quote = '') + { + $token = $this->str[$this->last]; + if ((!($flags = Context::isString($token))) && ($token !== $quote)) { return null; } - ++$idx; + $quote = $token; + + while (++$this->last < $this->len) { + if (($this->last + 1 < $this->len) + && ((($this->str[$this->last] === $quote) && ($this->str[$this->last + 1] === $quote)) + || (($this->str[$this->last] === '\\') && ($quote !== '`'))) + ) { + $token .= $this->str[$this->last] . $this->str[++$this->last]; + } else { + if ($this->str[$this->last] === $quote) { + break; + } + $token .= $this->str[$this->last]; + } + } + + if (($this->last >= $this->len) || ($this->str[$this->last] !== $quote)) { + $this->error( + sprintf( + __('Ending quote %1$s was expected.'), + $quote + ), + '', + $this->last + ); + } else { + $token .= $this->str[$this->last]; + } + return new Token($token, Token::TYPE_STRING, $flags); } - $this->last += $this->delimiterLen - 1; - return new Token($this->delimiter, Token::TYPE_DELIMITER); + /** + * Parses a symbol. + * + * @return Token + */ + public function parseSymbol() + { + $token = $this->str[$this->last]; + if (!($flags = Context::isSymbol($token))) { + return null; + } + + if ($flags & Token::FLAG_SYMBOL_VARIABLE) { + ++$this->last; + } else { + $token = ''; + } + + if (($str = $this->parseString('`')) === null) { + if (($str = static::parseUnknown()) === null) { + $this->error( + __('Variable name was expected.'), + $this->str[$this->last], + $this->last + ); + } + } + + if ($str !== null) { + $token .= $str->token; + } + + return new Token($token, Token::TYPE_SYMBOL, $flags); + } + + /** + * Parses unknown parts of the query. + * + * @return Token + */ + public function parseUnknown() + { + $token = $this->str[$this->last]; + if (Context::isSeparator($token)) { + return null; + } + while ((++$this->last < $this->len) && (!Context::isSeparator($this->str[$this->last]))) { + $token .= $this->str[$this->last]; + } + --$this->last; + return new Token($token); + } + + /** + * Parses the delimiter of the query. + * + * @return Token + */ + public function parseDelimiter() + { + $idx = 0; + + while ($idx < $this->delimiterLen) { + if ($this->delimiter[$idx] !== $this->str[$this->last + $idx]) { + return null; + } + ++$idx; + } + + $this->last += $this->delimiterLen - 1; + return new Token($this->delimiter, Token::TYPE_DELIMITER); + } } } diff --git a/libraries/sql-parser/src/Parser.php b/libraries/sql-parser/src/Parser.php index 888a9d4d3b..f00e442d95 100644 --- a/libraries/sql-parser/src/Parser.php +++ b/libraries/sql-parser/src/Parser.php @@ -7,410 +7,436 @@ * * @package SqlParser */ -namespace SqlParser; -use SqlParser\Statements\SelectStatement; -use SqlParser\Exceptions\ParserException; +namespace { -/** - * Takes multiple tokens (contained in a Lexer instance) as input and builds a - * parse tree. - * - * @category Parser - * @package SqlParser - * @author Dan Ungureanu - * @license http://opensource.org/licenses/GPL-2.0 GNU Public License - */ -class Parser -{ - - /** - * Array of classes that are used in parsing the SQL statements. - * - * @var array - */ - public static $STATEMENT_PARSERS = array( - - 'EXPLAIN' => 'SqlParser\\Statements\\ExplainStatement', - - // Table Maintenance Statements - // https://dev.mysql.com/doc/refman/5.7/en/table-maintenance-sql.html - 'ANALYZE' => 'SqlParser\\Statements\\AnalyzeStatement', - 'BACKUP' => 'SqlParser\\Statements\\BackupStatement', - 'CHECK' => 'SqlParser\\Statements\\CheckStatement', - 'CHECKSUM' => 'SqlParser\\Statements\\ChecksumStatement', - 'OPTIMIZE' => 'SqlParser\\Statements\\OptimizeStatement', - 'REPAIR' => 'SqlParser\\Statements\\RepairStatement', - 'RESTORE' => 'SqlParser\\Statements\\RestoreStatement', - - // Database Administration Statements - // https://dev.mysql.com/doc/refman/5.7/en/sql-syntax-server-administration.html - 'SET' => '', - 'SHOW' => 'SqlParser\\Statements\\ShowStatement', - - // Data Definition Statements. - // https://dev.mysql.com/doc/refman/5.7/en/sql-syntax-data-definition.html - 'ALTER' => 'SqlParser\\Statements\\AlterStatement', - 'CREATE' => 'SqlParser\\Statements\\CreateStatement', - 'DROP' => 'SqlParser\\Statements\\DropStatement', - 'RENAME' => 'SqlParser\\Statements\\RenameStatement', - 'TRUNCATE' => 'SqlParser\\Statements\\TruncateStatement', - - // Data Manipulation Statements. - // https://dev.mysql.com/doc/refman/5.7/en/sql-syntax-data-manipulation.html - 'CALL' => 'SqlParser\\Statements\\CallStatement', - 'DELETE' => 'SqlParser\\Statements\\DeleteStatement', - 'DO' => '', - 'HANDLER' => '', - 'INSERT' => 'SqlParser\\Statements\\InsertStatement', - 'LOAD' => '', - 'REPLACE' => 'SqlParser\\Statements\\ReplaceStatement', - 'SELECT' => 'SqlParser\\Statements\\SelectStatement', - 'UPDATE' => 'SqlParser\\Statements\\UpdateStatement', - - // Prepared Statements. - // https://dev.mysql.com/doc/refman/5.7/en/sql-syntax-prepared-statements.html - 'PREPARE' => '', - 'EXECUTE' => '', - ); - - /** - * Array of classes that are used in parsing SQL components. - * - * @var array - */ - public static $KEYWORD_PARSERS = array( - - // This is not a proper keyword and was added here to help the builder. - '_OPTIONS' => array( - 'class' => 'SqlParser\\Components\\OptionsArray', - 'field' => 'options', - ), - - 'ALTER' => array( - 'class' => 'SqlParser\\Components\\Expression', - 'field' => 'table', - 'options' => array('skipColumn' => true), - ), - 'ANALYZE' => array( - 'class' => 'SqlParser\\Components\\ExpressionArray', - 'field' => 'tables', - 'options' => array('skipColumn' => true), - ), - 'BACKUP' => array( - 'class' => 'SqlParser\\Components\\ExpressionArray', - 'field' => 'tables', - 'options' => array('skipColumn' => true), - ), - 'CALL' => array( - 'class' => 'SqlParser\\Components\\FunctionCall', - 'field' => 'call', - ), - 'CHECK' => array( - 'class' => 'SqlParser\\Components\\ExpressionArray', - 'field' => 'tables', - 'options' => array('skipColumn' => true), - ), - 'CHECKSUM' => array( - 'class' => 'SqlParser\\Components\\ExpressionArray', - 'field' => 'tables', - 'options' => array('skipColumn' => true), - ), - 'DROP' => array( - 'class' => 'SqlParser\\Components\\ExpressionArray', - 'field' => 'fields', - 'options' => array('skipColumn' => true), - ), - 'FROM' => array( - 'class' => 'SqlParser\\Components\\ExpressionArray', - 'field' => 'from', - 'options' => array('skipColumn' => true), - ), - 'GROUP BY' => array( - 'class' => 'SqlParser\\Components\\OrderKeyword', - 'field' => 'group', - ), - 'HAVING' => array( - 'class' => 'SqlParser\\Components\\Condition', - 'field' => 'having', - ), - 'INTO' => array( - 'class' => 'SqlParser\\Components\\IntoKeyword', - 'field' => 'into', - ), - 'JOIN' => array( - 'class' => 'SqlParser\\Components\\JoinKeyword', - 'field' => 'join', - ), - 'LEFT JOIN' => array( - 'class' => 'SqlParser\\Components\\JoinKeyword', - 'field' => 'join', - ), - 'RIGHT JOIN' => array( - 'class' => 'SqlParser\\Components\\JoinKeyword', - 'field' => 'join', - ), - 'INNER JOIN' => array( - 'class' => 'SqlParser\\Components\\JoinKeyword', - 'field' => 'join', - ), - 'FULL JOIN' => array( - 'class' => 'SqlParser\\Components\\JoinKeyword', - 'field' => 'join', - ), - 'LIMIT' => array( - 'class' => 'SqlParser\\Components\\Limit', - 'field' => 'limit', - ), - 'OPTIMIZE' => array( - 'class' => 'SqlParser\\Components\\ExpressionArray', - 'field' => 'tables', - 'options' => array('skipColumn' => true), - ), - 'ORDER BY' => array( - 'class' => 'SqlParser\\Components\\OrderKeyword', - 'field' => 'order', - ), - 'PARTITION' => array( - 'class' => 'SqlParser\\Components\\ArrayObj', - 'field' => 'partition', - ), - 'PROCEDURE' => array( - 'class' => 'SqlParser\\Components\\FunctionCall', - 'field' => 'procedure', - ), - 'RENAME' => array( - 'class' => 'SqlParser\\Components\\RenameOperation', - 'field' => 'renames', - ), - 'REPAIR' => array( - 'class' => 'SqlParser\\Components\\ExpressionArray', - 'field' => 'tables', - 'options' => array('skipColumn' => true), - ), - 'RESTORE' => array( - 'class' => 'SqlParser\\Components\\ExpressionArray', - 'field' => 'tables', - 'options' => array('skipColumn' => true), - ), - 'SET' => array( - 'class' => 'SqlParser\\Components\\SetOperation', - 'field' => 'set', - ), - 'SELECT' => array( - 'class' => 'SqlParser\\Components\\ExpressionArray', - 'field' => 'expr', - ), - 'TRUNCATE' => array( - 'class' => 'SqlParser\\Components\\Expression', - 'field' => 'table', - 'options' => array('skipColumn' => true), - ), - 'UPDATE' => array( - 'class' => 'SqlParser\\Components\\ExpressionArray', - 'field' => 'tables', - 'options' => array('skipColumn' => true), - ), - 'VALUE' => array( - 'class' => 'SqlParser\\Components\\Array2d', - 'field' => 'values', - ), - 'VALUES' => array( - 'class' => 'SqlParser\\Components\\Array2d', - 'field' => 'values', - ), - 'WHERE' => array( - 'class' => 'SqlParser\\Components\\Condition', - 'field' => 'where', - ), - - ); - - /** - * The list of tokens that are parsed. - * - * @var TokensList - */ - public $list; - - /** - * Whether errors should throw exceptions or just be stored. - * - * @var bool - * - * @see static::$errors - */ - public $strict = false; - - /** - * List of errors that occurred during parsing. - * - * Usually, the parsing does not stop once an error occurred because that - * error might be a false positive or a partial result (even a bad one) - * might be needed. - * - * @var ParserException[] - * - * @see Parser::error() - */ - public $errors = array(); - - /** - * List of statements parsed. - * - * @var Statement[] - */ - public $statements = array(); - - /** - * Constructor. - * - * @param mixed $list The list of tokens to be parsed. - * @param bool $strict Whether strict mode should be enabled or not. - */ - public function __construct($list = null, $strict = false) - { - if ((is_string($list)) || ($list instanceof UtfString)) { - $lexer = new Lexer($list, $strict); - $this->list = $lexer->list; - } elseif ($list instanceof TokensList) { - $this->list = $list; - } - - $this->strict = $strict; - - if ($list !== null) { - $this->parse(); - } - } - - /** - * Builds the parse trees. - * - * @return void - */ - public function parse() - { + if (!function_exists('__')) { /** - * Last parsed statement. - * @var Statement $lastStatement + * Translates the given string. + * + * @param string $str String to be translated. + * + * @return string */ - $lastStatement = null; - - /** - * Whether a union is parsed or not. - * @var bool $inUnion - */ - $inUnion = true; - - /** - * The index of the last token from the last statement. - * @var int $prevLastIdx - */ - $prevLastIdx = -1; - - /** - * The list of tokens. - * @var TokensList $list - */ - $list = &$this->list; - - for (; $list->idx < $list->count; ++$list->idx) { - - /** - * Token parsed at this moment. - * @var Token $token - */ - $token = $list->tokens[$list->idx]; - - // Statements can start with keywords only. - // Comments, whitespaces, etc. are ignored. - if ($token->type !== Token::TYPE_KEYWORD) { - if (($token->type !== TOKEN::TYPE_COMMENT) - && ($token->type !== Token::TYPE_WHITESPACE) - && ($token->type !== Token::TYPE_OPERATOR) // `(` and `)` - && ($token->type !== Token::TYPE_DELIMITER) - ) { - $this->error('Unexpected beginning of statement.', $token); - } - continue; - } - - if ($token->value === 'UNION') { - $inUnion = true; - continue; - } - - // Checking if it is a known statement that can be parsed. - if (empty(static::$STATEMENT_PARSERS[$token->value])) { - $this->error('Unrecognized statement type.', $token); - // Skipping to the end of this statement. - $list->getNextOfType(Token::TYPE_DELIMITER); - // - $prevLastIdx = $list->idx; - continue; - } - - /** - * The name of the class that is used for parsing. - * @var string $class - */ - $class = static::$STATEMENT_PARSERS[$token->value]; - - /** - * Processed statement. - * @var Statement $statement - */ - $statement = new $class($this, $this->list); - - // The first token that is a part of this token is the next token - // unprocessed by the previous statement. - // There might be brackets around statements and this shouldn't - // affect the parser - $statement->first = $prevLastIdx + 1; - - // Storing the index of the last token parsed and updating the old - // index. - $statement->last = $list->idx; - $prevLastIdx = $list->idx; - - // Finally, storing the statement. - if (($inUnion) - && ($lastStatement instanceof SelectStatement) - && ($statement instanceof SelectStatement) - ) { - /** - * Last SELECT statement. - * @var SelectStatement $lastStatement - */ - $lastStatement->union[] = $statement; - $inUnion = false; - } else { - $this->statements[] = $statement; - $lastStatement = $statement; - } - + function __($str) + { + return $str; + } + } +} + +namespace SqlParser { + + use SqlParser\Statements\SelectStatement; + use SqlParser\Exceptions\ParserException; + + /** + * Takes multiple tokens (contained in a Lexer instance) as input and builds a + * parse tree. + * + * @category Parser + * @package SqlParser + * @author Dan Ungureanu + * @license http://opensource.org/licenses/GPL-2.0 GNU Public License + */ + class Parser + { + + /** + * Array of classes that are used in parsing the SQL statements. + * + * @var array + */ + public static $STATEMENT_PARSERS = array( + + 'EXPLAIN' => 'SqlParser\\Statements\\ExplainStatement', + + // Table Maintenance Statements + // https://dev.mysql.com/doc/refman/5.7/en/table-maintenance-sql.html + 'ANALYZE' => 'SqlParser\\Statements\\AnalyzeStatement', + 'BACKUP' => 'SqlParser\\Statements\\BackupStatement', + 'CHECK' => 'SqlParser\\Statements\\CheckStatement', + 'CHECKSUM' => 'SqlParser\\Statements\\ChecksumStatement', + 'OPTIMIZE' => 'SqlParser\\Statements\\OptimizeStatement', + 'REPAIR' => 'SqlParser\\Statements\\RepairStatement', + 'RESTORE' => 'SqlParser\\Statements\\RestoreStatement', + + // Database Administration Statements + // https://dev.mysql.com/doc/refman/5.7/en/sql-syntax-server-administration.html + 'SET' => '', + 'SHOW' => 'SqlParser\\Statements\\ShowStatement', + + // Data Definition Statements. + // https://dev.mysql.com/doc/refman/5.7/en/sql-syntax-data-definition.html + 'ALTER' => 'SqlParser\\Statements\\AlterStatement', + 'CREATE' => 'SqlParser\\Statements\\CreateStatement', + 'DROP' => 'SqlParser\\Statements\\DropStatement', + 'RENAME' => 'SqlParser\\Statements\\RenameStatement', + 'TRUNCATE' => 'SqlParser\\Statements\\TruncateStatement', + + // Data Manipulation Statements. + // https://dev.mysql.com/doc/refman/5.7/en/sql-syntax-data-manipulation.html + 'CALL' => 'SqlParser\\Statements\\CallStatement', + 'DELETE' => 'SqlParser\\Statements\\DeleteStatement', + 'DO' => '', + 'HANDLER' => '', + 'INSERT' => 'SqlParser\\Statements\\InsertStatement', + 'LOAD' => '', + 'REPLACE' => 'SqlParser\\Statements\\ReplaceStatement', + 'SELECT' => 'SqlParser\\Statements\\SelectStatement', + 'UPDATE' => 'SqlParser\\Statements\\UpdateStatement', + + // Prepared Statements. + // https://dev.mysql.com/doc/refman/5.7/en/sql-syntax-prepared-statements.html + 'PREPARE' => '', + 'EXECUTE' => '', + ); + + /** + * Array of classes that are used in parsing SQL components. + * + * @var array + */ + public static $KEYWORD_PARSERS = array( + + // This is not a proper keyword and was added here to help the builder. + '_OPTIONS' => array( + 'class' => 'SqlParser\\Components\\OptionsArray', + 'field' => 'options', + ), + + 'ALTER' => array( + 'class' => 'SqlParser\\Components\\Expression', + 'field' => 'table', + 'options' => array('skipColumn' => true), + ), + 'ANALYZE' => array( + 'class' => 'SqlParser\\Components\\ExpressionArray', + 'field' => 'tables', + 'options' => array('skipColumn' => true), + ), + 'BACKUP' => array( + 'class' => 'SqlParser\\Components\\ExpressionArray', + 'field' => 'tables', + 'options' => array('skipColumn' => true), + ), + 'CALL' => array( + 'class' => 'SqlParser\\Components\\FunctionCall', + 'field' => 'call', + ), + 'CHECK' => array( + 'class' => 'SqlParser\\Components\\ExpressionArray', + 'field' => 'tables', + 'options' => array('skipColumn' => true), + ), + 'CHECKSUM' => array( + 'class' => 'SqlParser\\Components\\ExpressionArray', + 'field' => 'tables', + 'options' => array('skipColumn' => true), + ), + 'DROP' => array( + 'class' => 'SqlParser\\Components\\ExpressionArray', + 'field' => 'fields', + 'options' => array('skipColumn' => true), + ), + 'FROM' => array( + 'class' => 'SqlParser\\Components\\ExpressionArray', + 'field' => 'from', + 'options' => array('skipColumn' => true), + ), + 'GROUP BY' => array( + 'class' => 'SqlParser\\Components\\OrderKeyword', + 'field' => 'group', + ), + 'HAVING' => array( + 'class' => 'SqlParser\\Components\\Condition', + 'field' => 'having', + ), + 'INTO' => array( + 'class' => 'SqlParser\\Components\\IntoKeyword', + 'field' => 'into', + ), + 'JOIN' => array( + 'class' => 'SqlParser\\Components\\JoinKeyword', + 'field' => 'join', + ), + 'LEFT JOIN' => array( + 'class' => 'SqlParser\\Components\\JoinKeyword', + 'field' => 'join', + ), + 'RIGHT JOIN' => array( + 'class' => 'SqlParser\\Components\\JoinKeyword', + 'field' => 'join', + ), + 'INNER JOIN' => array( + 'class' => 'SqlParser\\Components\\JoinKeyword', + 'field' => 'join', + ), + 'FULL JOIN' => array( + 'class' => 'SqlParser\\Components\\JoinKeyword', + 'field' => 'join', + ), + 'LIMIT' => array( + 'class' => 'SqlParser\\Components\\Limit', + 'field' => 'limit', + ), + 'OPTIMIZE' => array( + 'class' => 'SqlParser\\Components\\ExpressionArray', + 'field' => 'tables', + 'options' => array('skipColumn' => true), + ), + 'ORDER BY' => array( + 'class' => 'SqlParser\\Components\\OrderKeyword', + 'field' => 'order', + ), + 'PARTITION' => array( + 'class' => 'SqlParser\\Components\\ArrayObj', + 'field' => 'partition', + ), + 'PROCEDURE' => array( + 'class' => 'SqlParser\\Components\\FunctionCall', + 'field' => 'procedure', + ), + 'RENAME' => array( + 'class' => 'SqlParser\\Components\\RenameOperation', + 'field' => 'renames', + ), + 'REPAIR' => array( + 'class' => 'SqlParser\\Components\\ExpressionArray', + 'field' => 'tables', + 'options' => array('skipColumn' => true), + ), + 'RESTORE' => array( + 'class' => 'SqlParser\\Components\\ExpressionArray', + 'field' => 'tables', + 'options' => array('skipColumn' => true), + ), + 'SET' => array( + 'class' => 'SqlParser\\Components\\SetOperation', + 'field' => 'set', + ), + 'SELECT' => array( + 'class' => 'SqlParser\\Components\\ExpressionArray', + 'field' => 'expr', + ), + 'TRUNCATE' => array( + 'class' => 'SqlParser\\Components\\Expression', + 'field' => 'table', + 'options' => array('skipColumn' => true), + ), + 'UPDATE' => array( + 'class' => 'SqlParser\\Components\\ExpressionArray', + 'field' => 'tables', + 'options' => array('skipColumn' => true), + ), + 'VALUE' => array( + 'class' => 'SqlParser\\Components\\Array2d', + 'field' => 'values', + ), + 'VALUES' => array( + 'class' => 'SqlParser\\Components\\Array2d', + 'field' => 'values', + ), + 'WHERE' => array( + 'class' => 'SqlParser\\Components\\Condition', + 'field' => 'where', + ), + + ); + + /** + * The list of tokens that are parsed. + * + * @var TokensList + */ + public $list; + + /** + * Whether errors should throw exceptions or just be stored. + * + * @var bool + * + * @see static::$errors + */ + public $strict = false; + + /** + * List of errors that occurred during parsing. + * + * Usually, the parsing does not stop once an error occurred because that + * error might be a false positive or a partial result (even a bad one) + * might be needed. + * + * @var ParserException[] + * + * @see Parser::error() + */ + public $errors = array(); + + /** + * List of statements parsed. + * + * @var Statement[] + */ + public $statements = array(); + + /** + * Constructor. + * + * @param mixed $list The list of tokens to be parsed. + * @param bool $strict Whether strict mode should be enabled or not. + */ + public function __construct($list = null, $strict = false) + { + if ((is_string($list)) || ($list instanceof UtfString)) { + $lexer = new Lexer($list, $strict); + $this->list = $lexer->list; + } elseif ($list instanceof TokensList) { + $this->list = $list; + } + + $this->strict = $strict; + + if ($list !== null) { + $this->parse(); + } + } + + /** + * Builds the parse trees. + * + * @return void + */ + public function parse() + { + + /** + * Last parsed statement. + * @var Statement $lastStatement + */ + $lastStatement = null; + + /** + * Whether a union is parsed or not. + * @var bool $inUnion + */ + $inUnion = true; + + /** + * The index of the last token from the last statement. + * @var int $prevLastIdx + */ + $prevLastIdx = -1; + + /** + * The list of tokens. + * @var TokensList $list + */ + $list = &$this->list; + + for (; $list->idx < $list->count; ++$list->idx) { + + /** + * Token parsed at this moment. + * @var Token $token + */ + $token = $list->tokens[$list->idx]; + + // Statements can start with keywords only. + // Comments, whitespaces, etc. are ignored. + if ($token->type !== Token::TYPE_KEYWORD) { + if (($token->type !== TOKEN::TYPE_COMMENT) + && ($token->type !== Token::TYPE_WHITESPACE) + && ($token->type !== Token::TYPE_OPERATOR) // `(` and `)` + && ($token->type !== Token::TYPE_DELIMITER) + ) { + $this->error( + __('Unexpected beginning of statement.'), + $token + ); + } + continue; + } + + if ($token->value === 'UNION') { + $inUnion = true; + continue; + } + + // Checking if it is a known statement that can be parsed. + if (empty(static::$STATEMENT_PARSERS[$token->value])) { + $this->error( + __('Unrecognized statement type.'), + $token + ); + // Skipping to the end of this statement. + $list->getNextOfType(Token::TYPE_DELIMITER); + // + $prevLastIdx = $list->idx; + continue; + } + + /** + * The name of the class that is used for parsing. + * @var string $class + */ + $class = static::$STATEMENT_PARSERS[$token->value]; + + /** + * Processed statement. + * @var Statement $statement + */ + $statement = new $class($this, $this->list); + + // The first token that is a part of this token is the next token + // unprocessed by the previous statement. + // There might be brackets around statements and this shouldn't + // affect the parser + $statement->first = $prevLastIdx + 1; + + // Storing the index of the last token parsed and updating the old + // index. + $statement->last = $list->idx; + $prevLastIdx = $list->idx; + + // Finally, storing the statement. + if (($inUnion) + && ($lastStatement instanceof SelectStatement) + && ($statement instanceof SelectStatement) + ) { + /** + * Last SELECT statement. + * @var SelectStatement $lastStatement + */ + $lastStatement->union[] = $statement; + $inUnion = false; + } else { + $this->statements[] = $statement; + $lastStatement = $statement; + } + + } + } + + /** + * Creates a new error log. + * + * @param string $msg The error message. + * @param Token $token The token that produced the error. + * @param int $code The code of the error. + * + * @throws ParserException Throws the exception, if strict mode is enabled. + * + * @return void + */ + public function error($msg = '', Token $token = null, $code = 0) + { + $error = new ParserException($msg, $token, $code); + if ($this->strict) { + throw $error; + } + $this->errors[] = $error; } } - - /** - * Creates a new error log. - * - * @param string $msg The error message. - * @param Token $token The token that produced the error. - * @param int $code The code of the error. - * - * @throws ParserException Throws the exception, if strict mode is enabled. - * - * @return void - */ - public function error($msg = '', Token $token = null, $code = 0) - { - $error = new ParserException($msg, $token, $code); - if ($this->strict) { - throw $error; - } - $this->errors[] = $error; - } } diff --git a/libraries/sql-parser/src/Statement.php b/libraries/sql-parser/src/Statement.php index 277942daba..1494a0dd52 100644 --- a/libraries/sql-parser/src/Statement.php +++ b/libraries/sql-parser/src/Statement.php @@ -198,7 +198,7 @@ abstract class Statement if (($token->type !== TOKEN::TYPE_COMMENT) && ($token->type !== Token::TYPE_WHITESPACE) ) { - $parser->error('Unexpected token.', $token); + $parser->error(__('Unexpected token.'), $token); } continue; } @@ -248,10 +248,7 @@ abstract class Statement } elseif ($class === null) { // There is no parser for this keyword and isn't the beginning // of a statement (so no options) either. - $parser->error( - 'Unrecognized keyword.', - $token - ); + $parser->error(__('Unrecognized keyword.'), $token); continue; } diff --git a/libraries/sql-parser/src/Statements/CreateStatement.php b/libraries/sql-parser/src/Statements/CreateStatement.php index afabc6a8c5..3380f1cd8a 100644 --- a/libraries/sql-parser/src/Statements/CreateStatement.php +++ b/libraries/sql-parser/src/Statements/CreateStatement.php @@ -290,7 +290,7 @@ class CreateStatement extends Statement if (empty($this->name)) { $parser->error( - 'The name of the entity was expected.', + __('The name of the entity was expected.'), $list->tokens[$list->idx] ); } else { @@ -307,7 +307,7 @@ class CreateStatement extends Statement $this->fields = FieldDefinition::parse($parser, $list); if (empty($this->fields)) { $parser->error( - 'At least one field definition was expected.', + __('At least one field definition was expected.'), $list->tokens[$list->idx] ); } @@ -326,7 +326,7 @@ class CreateStatement extends Statement $token = $list->getNextOfType(Token::TYPE_KEYWORD); if ($token->value !== 'RETURNS') { $parser->error( - 'A \'RETURNS\' keyword was expected.', + __('A "RETURNS" keyword was expected.'), $token ); } else { diff --git a/lint.php b/lint.php index b1d4bb414c..f94f6316eb 100644 --- a/lint.php +++ b/lint.php @@ -8,31 +8,32 @@ define('PHPMYADMIN', true); -// We load the minimum files required to check if the user is logged in. -require_once 'libraries/core.lib.php'; -require_once 'libraries/Config.class.php'; -$GLOBALS['PMA_Config'] = new PMA_Config(CONFIG_FILE); -require_once 'libraries/session.inc.php'; - -// If user is not logged in, he should not send any requests, so we exit here to -// avoid external requests. -if (empty($_SESSION['encryption_key'])) { - // Unauthorized access detected. - exit; -} +/** + * The SQL query to be analyzed. + * + * This does not need to be checked again XSS or MySQL injections because it is + * never executed, just parsed. + * + * The client, which will recieve the JSON response will decode the message and + * and any HTML fragments that are displayed to the user will be encoded anyway. + * + * @var string + */ +$sql_query = $_REQUEST['sql_query']; /** - * Loads the SQL lexer and parser, which are used to detect errors. + * Loading common files. Used to check for authorization, localization and to + * load the parsing library. */ -require_once 'libraries/sql-parser/autoload.php'; +require_once 'libraries/common.inc.php'; /** * Loads the linter. */ require_once 'libraries/Linter.class.php'; -// The input of this function does not need to be checked again XSS or MySQL -// injections because it is never executed, just parsed. -// The client, which will recieve the JSON response will decode the message and -// and any HTML fragments that are displayed to the user will be encoded anyway. -PMA_Linter::lint($_REQUEST['sql_query']); +// Disabling standard response. +$response = PMA_Response::getInstance(); +$response->disable(); + +echo json_encode(PMA_Linter::lint($sql_query)); diff --git a/test/libraries/PMA_Linter_Test.php b/test/libraries/PMA_Linter_Test.php index 4c3b6cd6c5..f61724b4e2 100644 --- a/test/libraries/PMA_Linter_Test.php +++ b/test/libraries/PMA_Linter_Test.php @@ -74,8 +74,7 @@ class PMA_Linter_Test extends PHPUnit_Framework_TestCase */ public function testLintEmpty() { - $this->expectOutputString('[]'); - PMA_Linter::lint(''); + $this->assertEquals(array(), PMA_Linter::lint('')); } /** @@ -85,8 +84,7 @@ class PMA_Linter_Test extends PHPUnit_Framework_TestCase */ public function testLintNoErrors() { - $this->expectOutputString('[]'); - PMA_Linter::lint('SELECT * FROM tbl'); + $this->assertEquals(array(), PMA_Linter::lint('SELECT * FROM tbl')); } /** @@ -96,29 +94,27 @@ class PMA_Linter_Test extends PHPUnit_Framework_TestCase */ public function testLintErrors() { - $this->expectOutputString( - json_encode( + $this->assertEquals( + array( array( - array( - 'message' => 'Unrecognized data type. (near IN)', - 'fromLine' => 0, - 'fromColumn' => 22, - 'toLine' => 0, - 'toColumn' => 24, - 'severity' => 'error', - ), - array( - 'message' => 'A closing bracket was expected. (near IN)', - 'fromLine' => 0, - 'fromColumn' => 22, - 'toLine' => 0, - 'toColumn' => 24, - 'severity' => 'error', - ) + 'message' => 'Unrecognized data type. (near IN)', + 'fromLine' => 0, + 'fromColumn' => 22, + 'toLine' => 0, + 'toColumn' => 24, + 'severity' => 'error', + ), + array( + 'message' => 'A closing bracket was expected. (near IN)', + 'fromLine' => 0, + 'fromColumn' => 22, + 'toLine' => 0, + 'toColumn' => 24, + 'severity' => 'error', ) - ) + ), + PMA_Linter::lint('CREATE TABLE tbl ( id IN') ); - PMA_Linter::lint('CREATE TABLE tbl ( id IN'); } /** @@ -128,20 +124,18 @@ class PMA_Linter_Test extends PHPUnit_Framework_TestCase */ public function testLongQuery() { - $this->expectOutputString( - json_encode( + $this->assertEquals( + array( array( - array( - 'message' => 'Linting is disabled for this query because it exceeds the maximum length.', - 'fromLine' => 0, - 'fromColumn' => 0, - 'toLine' => 0, - 'toColumn' => 0, - 'severity' => 'warning', - ) + 'message' => 'Linting is disabled for this query because it exceeds the maximum length.', + 'fromLine' => 0, + 'fromColumn' => 0, + 'toLine' => 0, + 'toColumn' => 0, + 'severity' => 'warning', ) - ) + ), + PMA_Linter::lint(str_repeat(";", 10001)) ); - PMA_Linter::lint(str_repeat(";", 10001)); } }