From e7928e58ccf1f8e1fa82110a0bb03b5764b61198 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C4=8Ciha=C5=99?= Date: Fri, 20 Jan 2017 14:05:10 +0100 Subject: [PATCH] Update SQL parser to 3.4.17 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #12894 Signed-off-by: Michal Čihař --- ChangeLog | 1 + libraries/sql-parser/src/Lexer.php | 156 +++++++++++-------- libraries/sql-parser/src/Utils/Formatter.php | 130 +++++++--------- 3 files changed, 146 insertions(+), 141 deletions(-) diff --git a/ChangeLog b/ChangeLog index 1c1b50034b..8857dd820b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -37,6 +37,7 @@ phpMyAdmin - ChangeLog - issue #12861 Fix renaming tables with lower_case_table_names=2 - issue #12876 Fix possible PHP error in navigation - issue #12881 Fix database search with newer php-gettext +- issue #12894 Fix linter error on unterminated variable name 4.6.5.2 (2016-12-05) - issue #12765 Fixed SQL export with newlines diff --git a/libraries/sql-parser/src/Lexer.php b/libraries/sql-parser/src/Lexer.php index 73c4100a84..8222013a54 100644 --- a/libraries/sql-parser/src/Lexer.php +++ b/libraries/sql-parser/src/Lexer.php @@ -185,18 +185,16 @@ class Lexer { // `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); + $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, 'UTF-8'))) { - $str = new UtfString($str); - } + if (!$str instanceof UtfString && USE_UTF_STRINGS && $len !== mb_strlen($str, 'UTF-8')) { + $str = new UtfString($str); } $this->str = $str; - $this->len = ($str instanceof UtfString) ? $str->length() : $len; + $this->len = $str instanceof UtfString ? $str->length() : $len; $this->strict = $strict; @@ -251,7 +249,7 @@ class Lexer $token = null; foreach (static::$PARSER_METHODS as $method) { - if (($token = $this->$method())) { + if ($token = $this->$method()) { break; } } @@ -264,12 +262,16 @@ class Lexer $this->str[$this->last], $this->last ); - } elseif (($lastToken !== null) - && ($token->type === Token::TYPE_SYMBOL) - && ($token->flags & Token::FLAG_SYMBOL_VARIABLE) - && (($lastToken->type === Token::TYPE_STRING) - || (($lastToken->type === Token::TYPE_SYMBOL) - && ($lastToken->flags & Token::FLAG_SYMBOL_BACKTICK))) + } elseif ($lastToken !== null + && $token->type === Token::TYPE_SYMBOL + && $token->flags & Token::FLAG_SYMBOL_VARIABLE + && ( + $lastToken->type === Token::TYPE_STRING + || ( + $lastToken->type === Token::TYPE_SYMBOL + && $lastToken->flags & Token::FLAG_SYMBOL_BACKTICK + ) + ) ) { // Handles ```... FROM 'user'@'%' ...```. $lastToken->token .= $token->token; @@ -277,10 +279,10 @@ class Lexer $lastToken->flags = Token::FLAG_SYMBOL_USER; $lastToken->value .= '@' . $token->value; continue; - } elseif (($lastToken !== null) - && ($token->type === Token::TYPE_KEYWORD) - && ($lastToken->type === Token::TYPE_OPERATOR) - && ($lastToken->value === '.') + } elseif ($lastToken !== null + && $token->type === Token::TYPE_KEYWORD + && $lastToken->type === Token::TYPE_OPERATOR + && $lastToken->value === '.' ) { // Handles ```... tbl.FROM ...```. In this case, FROM is not // a reserved word. @@ -294,7 +296,7 @@ class Lexer $list->tokens[$list->count++] = $token; // Handling delimiters. - if (($token->type === Token::TYPE_NONE) && ($token->value === 'DELIMITER')) { + if ($token->type === Token::TYPE_NONE && $token->value === 'DELIMITER') { if ($this->last + 1 >= $this->len) { $this->error( __('Expected whitespace(s) before delimiter.'), @@ -325,7 +327,7 @@ class Lexer // Parsing the delimiter. $this->delimiter = null; - while ((++$this->last < $this->len) && (!Context::isWhitespace($this->str[$this->last]))) { + while (++$this->last < $this->len && !Context::isWhitespace($this->str[$this->last])) { $this->delimiter .= $this->str[$this->last]; } @@ -419,16 +421,17 @@ class Lexer } 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); - $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. - } + $token .= $this->str[$this->last]; + if (($this->last + 1 === $this->len || Context::isSeparator($this->str[$this->last + 1])) + && $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. } } @@ -542,7 +545,7 @@ class Lexer return null; } - while ((++$this->last < $this->len) && (Context::isWhitespace($this->str[$this->last]))) { + while (++$this->last < $this->len && Context::isWhitespace($this->str[$this->last])) { $token .= $this->str[$this->last]; } @@ -563,10 +566,12 @@ class Lexer // Bash style comments. (#comment\n) if (Context::isComment($token)) { - while ((++$this->last < $this->len) && ($this->str[$this->last] !== "\n")) { + while ( + ++$this->last < $this->len + && $this->str[$this->last] !== "\n" + ) { $token .= $this->str[$this->last]; } - $token .= "\n"; // Adding the line ending. return new Token($token, Token::TYPE_COMMENT, Token::FLAG_COMMENT_BASH); } @@ -583,13 +588,16 @@ class Lexer } // Checking if this is a MySQL-specific command. - if (($this->last + 1 < $this->len) && ($this->str[$this->last + 1] === '!')) { + if ($this->last + 1 < $this->len + && $this->str[$this->last + 1] === '!' + ) { $flags |= Token::FLAG_COMMENT_MYSQL_CMD; $token .= $this->str[++$this->last]; - while ((++$this->last < $this->len) - && ('0' <= $this->str[$this->last]) - && ($this->str[$this->last] <= '9') + while ( + ++$this->last < $this->len + && '0' <= $this->str[$this->last] + && $this->str[$this->last] <= '9' ) { $token .= $this->str[$this->last]; } @@ -601,8 +609,12 @@ class Lexer } // Parsing the comment. - while ((++$this->last < $this->len) - && (($this->str[$this->last - 1] !== '*') || ($this->str[$this->last] !== '/')) + while ( + ++$this->last < $this->len + && ( + $this->str[$this->last - 1] !== '*' + || $this->str[$this->last] !== '/' + ) ) { $token .= $this->str[$this->last]; } @@ -622,10 +634,12 @@ class Lexer if (Context::isComment($token)) { // Checking if this comment did not end already (```--\n```). if ($this->str[$this->last] !== "\n") { - while ((++$this->last < $this->len) && ($this->str[$this->last] !== "\n")) { + while ( + ++$this->last < $this->len + && $this->str[$this->last] !== "\n" + ) { $token .= $this->str[$this->last]; } - $token .= "\n"; // Adding the line ending. } return new Token($token, Token::TYPE_COMMENT, Token::FLAG_COMMENT_SQL); @@ -719,14 +733,16 @@ class Lexer if ($state === 1) { if ($this->str[$this->last] === '-') { $flags |= Token::FLAG_NUMBER_NEGATIVE; - } elseif (($this->last + 1 < $this->len) - && ($this->str[$this->last] === '0') - && (($this->str[$this->last + 1] === 'x') - || ($this->str[$this->last + 1] === 'X')) + } elseif ($this->last + 1 < $this->len + && $this->str[$this->last] === '0' + && ( + $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')) { + } elseif ($this->str[$this->last] >= '0' && $this->str[$this->last] <= '9') { $state = 3; } elseif ($this->str[$this->last] === '.') { $state = 4; @@ -738,40 +754,43 @@ class Lexer } } 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'))) + 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')) { + } elseif ($this->str[$this->last] === 'e' || $this->str[$this->last] === 'E') { $state = 5; - } elseif (($this->str[$this->last] < '0') || ($this->str[$this->last] > '9')) { + } 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')) { + if ($this->str[$this->last] === 'e' || $this->str[$this->last] === 'E') { $state = 5; - } elseif (($this->str[$this->last] < '0') || ($this->str[$this->last] > '9')) { + } 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'))) + 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')) { + if ($this->str[$this->last] < '0' || $this->str[$this->last] > '9') { // Just digits are valid characters. break; } @@ -785,8 +804,8 @@ class Lexer } elseif ($state === 8) { if ($this->str[$this->last] === '\'') { $state = 9; - } elseif (($this->str[$this->last] !== '0') - && ($this->str[$this->last] !== '1') + } elseif ($this->str[$this->last] !== '0' + && $this->str[$this->last] !== '1' ) { break; } @@ -795,9 +814,9 @@ class Lexer } $token .= $this->str[$this->last]; } - if (($state === 2) || ($state === 3) - || (($token !== '.') && ($state === 4)) - || ($state === 6) || ($state === 9) + if ($state === 2 || $state === 3 + || ($token !== '.' && $state === 4) + || $state === 6 || $state === 9 ) { --$this->last; @@ -818,15 +837,17 @@ class Lexer public function parseString($quote = '') { $token = $this->str[$this->last]; - if ((!($flags = Context::isString($token))) && ($token !== $quote)) { + if (!($flags = Context::isString($token)) && $token !== $quote) { 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 !== '`'))) + 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 { @@ -837,7 +858,7 @@ class Lexer } } - if (($this->last >= $this->len) || ($this->str[$this->last] !== $quote)) { + if ($this->last >= $this->len || $this->str[$this->last] !== $quote) { $this->error( sprintf( __('Ending quote %1$s was expected.'), @@ -866,7 +887,7 @@ class Lexer } if ($flags & Token::FLAG_SYMBOL_VARIABLE) { - if ($this->str[++$this->last] === '@') { + if ($this->last + 1 < $this->len && $this->str[++$this->last] === '@') { // This is a system variable (e.g. `@@hostname`). $token .= $this->str[$this->last++]; $flags |= Token::FLAG_SYMBOL_SYSTEM; @@ -907,7 +928,8 @@ class Lexer if (Context::isSeparator($token)) { return null; } - while ((++$this->last < $this->len) && (!Context::isSeparator($this->str[$this->last]))) { + + while (++$this->last < $this->len && !Context::isSeparator($this->str[$this->last])) { $token .= $this->str[$this->last]; } --$this->last; @@ -924,7 +946,7 @@ class Lexer { $idx = 0; - while (($idx < $this->delimiterLen) && ($this->last + $idx < $this->len)) { + while ($idx < $this->delimiterLen && $this->last + $idx < $this->len) { if ($this->delimiter[$idx] !== $this->str[$this->last + $idx]) { return null; } diff --git a/libraries/sql-parser/src/Utils/Formatter.php b/libraries/sql-parser/src/Utils/Formatter.php index ec23ba9d98..b488a6a67a 100644 --- a/libraries/sql-parser/src/Utils/Formatter.php +++ b/libraries/sql-parser/src/Utils/Formatter.php @@ -78,6 +78,10 @@ class Formatter $options['line_ending'] = $options['type'] === 'html' ? '
' : "\n"; } + if (is_null($options['indentation'])) { + $options['indentation'] = $options['type'] === 'html' ? '    ' : ' '; + } + // `parts_newline` requires `clause_newline` $options['parts_newline'] &= $options['clause_newline']; @@ -112,7 +116,7 @@ class Formatter * * @var string */ - 'indentation' => ' ', + 'indentation' => null, /* * Whether comments should be removed or not. @@ -322,14 +326,6 @@ class Formatter */ $prev = null; - /** - * Comments are being formatted separately to maintain the whitespaces - * before and after them. - * - * @var string - */ - $comment = ''; - // In order to be able to format the queries correctly, the next token // must be taken into consideration. The loop below uses two pointers, // `$prev` and `$curr` which store two consecutive tokens. @@ -338,39 +334,26 @@ class Formatter /** * Token parsed at this moment. * - * @var Token + * @var Token $curr */ $curr = $list->tokens[$list->idx]; if ($curr->type === Token::TYPE_WHITESPACE) { // Whitespaces are skipped because the formatter adds its own. continue; - } elseif ($curr->type === Token::TYPE_COMMENT) { - // Whether the comments should be parsed. - if (!empty($this->options['remove_comments'])) { - continue; - } + } - if ($list->tokens[$list->idx - 1]->type === Token::TYPE_WHITESPACE) { - // The whitespaces before and after are preserved for - // formatting reasons. - $comment .= $list->tokens[$list->idx - 1]->token; - } - $comment .= $this->toString($curr); - if (($list->tokens[$list->idx + 1]->type === Token::TYPE_WHITESPACE) - && ($list->tokens[$list->idx + 2]->type !== Token::TYPE_COMMENT) - ) { - // Adding the next whitespace only there is no comment that - // follows it immediately which may cause adding a - // whitespace twice. - $comment .= $list->tokens[$list->idx + 1]->token; - } - - // Everything was handled here, no need to continue. + if ($curr->type === Token::TYPE_COMMENT && $this->options['remove_comments']) { + // Skip Comments if option `remove_comments` is enabled continue; } // Checking if pointers were initialized. + /** + * Previous Token. + * + * @var Token $prev + */ if ($prev !== null) { // Checking if a new clause started. if (static::isClause($prev) !== false) { @@ -379,12 +362,16 @@ class Formatter } // The options of a clause should stay on the same line and everything that follows. - if (($this->options['parts_newline']) - && (!$formattedOptions) - && (empty(self::$INLINE_CLAUSES[$lastClause])) - && (($curr->type !== Token::TYPE_KEYWORD) - || (($curr->type === Token::TYPE_KEYWORD) - && ($curr->flags & Token::FLAG_KEYWORD_FUNCTION))) + if ($this->options['parts_newline'] + && !$formattedOptions + && empty(self::$INLINE_CLAUSES[$lastClause]) + && ( + $curr->type !== Token::TYPE_KEYWORD + || ( + $curr->type === Token::TYPE_KEYWORD + && $curr->flags & Token::FLAG_KEYWORD_FUNCTION + ) + ) ) { $formattedOptions = true; $lineEnded = true; @@ -393,7 +380,7 @@ class Formatter // Checking if this clause ended. if ($tmp = static::isClause($curr)) { - if (($tmp == 2) || ($this->options['clause_newline'])) { + if ($tmp == 2 || $this->options['clause_newline']) { $lineEnded = true; if ($this->options['parts_newline']) { --$indent; @@ -402,24 +389,26 @@ class Formatter } // Indenting BEGIN ... END blocks. - if (($prev->type === Token::TYPE_KEYWORD) && ($prev->value === 'BEGIN')) { + if ($prev->type === Token::TYPE_KEYWORD && $prev->value === 'BEGIN') { $lineEnded = true; array_push($blocksIndentation, $indent); ++$indent; - } elseif (($curr->type === Token::TYPE_KEYWORD) && ($curr->value === 'END')) { + } elseif ($curr->type === Token::TYPE_KEYWORD && $curr->value === 'END') { $lineEnded = true; $indent = array_pop($blocksIndentation); } // Formatting fragments delimited by comma. - if (($prev->type === Token::TYPE_OPERATOR) && ($prev->value === ',')) { + if ($prev->type === Token::TYPE_OPERATOR && $prev->value === ',') { // Fragments delimited by a comma are broken into multiple // pieces only if the clause is not inlined or this fragment // is between brackets that are on new line. - if (((empty(self::$INLINE_CLAUSES[$lastClause])) - && !$shortGroup - && ($this->options['parts_newline'])) - || (end($blocksLineEndings) === true) + if (end($blocksLineEndings) === true + || ( + empty(self::$INLINE_CLAUSES[$lastClause]) + && !$shortGroup + && $this->options['parts_newline'] + ) ) { $lineEnded = true; } @@ -428,7 +417,7 @@ class Formatter // Handling brackets. // Brackets are indented only if the length of the fragment between // them is longer than 30 characters. - if (($prev->type === Token::TYPE_OPERATOR) && ($prev->value === '(')) { + if ($prev->type === Token::TYPE_OPERATOR && $prev->value === '(') { array_push($blocksIndentation, $indent); $shortGroup = true; if (static::getGroupLength($list) > 30) { @@ -437,18 +426,12 @@ class Formatter $shortGroup = false; } array_push($blocksLineEndings, $lineEnded); - } elseif (($curr->type === Token::TYPE_OPERATOR) && ($curr->value === ')')) { + } elseif ($curr->type === Token::TYPE_OPERATOR && $curr->value === ')') { $indent = array_pop($blocksIndentation); $lineEnded |= array_pop($blocksLineEndings); $shortGroup = false; } - // Delimiter must be placed on the same line with the last - // clause. - if ($curr->type === Token::TYPE_DELIMITER) { - $lineEnded = false; - } - // Adding the token. $ret .= $this->toString($prev); @@ -459,33 +442,29 @@ class Formatter $indent = 0; } - if ($curr->type !== Token::TYPE_COMMENT) { - $ret .= $this->options['line_ending'] - . str_repeat($this->options['indentation'], $indent); - } + $ret .= $this->options['line_ending'] + . str_repeat($this->options['indentation'], $indent); + $lineEnded = false; } else { // If the line ended there is no point in adding whitespaces. // Also, some tokens do not have spaces before or after them. - if (!((($prev->type === Token::TYPE_OPERATOR) && (($prev->value === '.') || ($prev->value === '('))) - // No space after . ( - || (($curr->type === Token::TYPE_OPERATOR) && (($curr->value === '.') || ($curr->value === ',') - || ($curr->value === '(') || ($curr->value === ')'))) - // No space before . , ( ) - || (($curr->type === Token::TYPE_DELIMITER)) && (mb_strlen($curr->value, 'UTF-8') < 2)) + if ( // A space after delimiters that are longer than 2 characters. - || ($prev->value === 'DELIMITER') + $prev->value === 'DELIMITER' + || !( + ($prev->type === Token::TYPE_OPERATOR && ($prev->value === '.' || $prev->value === '(')) + // No space after . ( + || ($curr->type === Token::TYPE_OPERATOR && ($curr->value === '.' || $curr->value === ',' || $curr->value === '(' || $curr->value === ')')) + // No space before . , ( ) + || $curr->type === Token::TYPE_DELIMITER && mb_strlen($curr->value, 'UTF-8') < 2 + ) ) { $ret .= ' '; } } } - if (!empty($comment)) { - $ret .= $comment; - $comment = ''; - } - // Iteration finished, consider current token as previous. $prev = $curr; } @@ -524,8 +503,8 @@ class Formatter $text = $token->token; foreach ($this->options['formats'] as $format) { - if (($token->type === $format['type']) - && (($token->flags & $format['flags']) === $format['flags']) + if ($token->type === $format['type'] + && ($token->flags & $format['flags']) === $format['flags'] ) { // Running transformation function. if (!empty($format['function'])) { @@ -626,11 +605,14 @@ class Formatter */ public static function isClause($token) { - if ((($token->type === Token::TYPE_NONE) && (strtoupper($token->token) === 'DELIMITER')) - || (($token->type === Token::TYPE_KEYWORD) && (isset(Parser::$STATEMENT_PARSERS[$token->value]))) + if ( + ($token->type === Token::TYPE_KEYWORD && isset(Parser::$STATEMENT_PARSERS[$token->value])) + || ($token->type === Token::TYPE_NONE && strtoupper($token->token) === 'DELIMITER') ) { return 2; - } elseif (($token->type === Token::TYPE_KEYWORD) && (isset(Parser::$KEYWORD_PARSERS[$token->value]))) { + } elseif ( + $token->type === Token::TYPE_KEYWORD && isset(Parser::$KEYWORD_PARSERS[$token->value]) + ) { return 1; }