diff options
-rw-r--r-- | .gitattributes | 8 | ||||
-rw-r--r-- | CHANGELOG.md | 10 | ||||
-rw-r--r-- | README.md | 7 | ||||
-rw-r--r-- | composer.json | 5 | ||||
-rw-r--r-- | phpunit.xml | 2 | ||||
-rw-r--r-- | src/Components/Expression.php | 1 | ||||
-rw-r--r-- | src/Lexer.php | 154 | ||||
-rw-r--r-- | src/Utils/Formatter.php | 66 | ||||
-rw-r--r-- | tests/Parser/SelectStatementTest.php | 2 | ||||
-rw-r--r-- | tests/TestCase.php (renamed from tests/bootstrap.php) | 3 | ||||
-rw-r--r-- | tests/Utils/CLITest.php | 8 | ||||
-rw-r--r-- | tests/Utils/FormatterTest.php | 60 | ||||
-rw-r--r-- | tests/data/parser/parseSelect11.in | 1 | ||||
-rw-r--r-- | tests/data/parser/parseSelect11.out | 4 | ||||
-rw-r--r-- | tests/data/parser/parseSelectErr2.in | 1 | ||||
-rw-r--r-- | tests/data/parser/parseSelectErr2.out | 4 | ||||
-rw-r--r-- | tools/ContextGenerator.php | 2 |
17 files changed, 210 insertions, 128 deletions
diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..63c9828 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,8 @@ +/tests export-ignore +/tools export-ignore +.gitattributes export-ignore +.gitignore export-ignore +.travis.yml export-ignore +codeconv.yml export-ignore +phpunit.xml export-ignore +.php_cs export-ignore diff --git a/CHANGELOG.md b/CHANGELOG.md index fe8df4d..a545678 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,13 +2,21 @@ ## [Unreleased] +* Coding style fixes. +* Fixed indentation in HTML formatting. +* Fixed parsing of unterminated variables. + +## [3.4.16] - 2017-01-06 + +* Coding style fixes. +* Properly handle operators AND, NOT, OR, XOR, DIV, MOD + ## [3.4.15] - 2017-01-02 * Fix return value of Formatter.toString() when type is text * Fix parsing of FIELDS and LINES options in SELECT..INTO * PHP 7.2 compatibility. * Better parameter passing to query formatter. -* Coding style fixes. ## [3.4.14] - 2016-11-30 @@ -77,6 +77,13 @@ $parser->statements[0]->from[0] = $table2; $statement = $parser->statements[0]; $query2 = $statement->build(); var_dump($query2); // outputs string(19) "SELECT * FROM `b` " + +// Change SQL mode +SqlParser\Context::setMode('ANSI_QUOTES'); + +// build the query again using different quotes +$query2 = $statement->build(); +var_dump($query2); // outputs string(19) "SELECT * FROM "b" " ``` ## More information diff --git a/composer.json b/composer.json index 4eb69e9..0b59e56 100644 --- a/composer.json +++ b/composer.json @@ -32,5 +32,10 @@ "psr-4": { "SqlParser\\": "src" } + }, + "autoload-dev": { + "psr-4": { + "SqlParser\\Tests\\": "tests" + } } } diff --git a/phpunit.xml b/phpunit.xml index f307622..ac69326 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <phpunit backupGlobals="false" backupStaticAttributes="false" - bootstrap="tests/bootstrap.php" + bootstrap="vendor/autoload.php" colors="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true" diff --git a/src/Components/Expression.php b/src/Components/Expression.php index 9c74b20..04466b0 100644 --- a/src/Components/Expression.php +++ b/src/Components/Expression.php @@ -30,6 +30,7 @@ class Expression extends Component */ private static $ALLOWED_KEYWORDS = array( 'AS' => 1, 'DUAL' => 1, 'NULL' => 1, 'REGEXP' => 1, 'CASE' => 1, + 'DIV' => 1, 'AND' => 1, 'OR' => 1, 'XOR' => 1, 'NOT' => 1, 'MOD' => 1, ); /** diff --git a/src/Lexer.php b/src/Lexer.php index 3ab5f5a..08dd09a 100644 --- a/src/Lexer.php +++ b/src/Lexer.php @@ -161,18 +161,16 @@ class Lexer extends Core { // `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; @@ -227,7 +225,7 @@ class Lexer extends Core $token = null; foreach (static::$PARSER_METHODS as $method) { - if (($token = $this->$method())) { + if ($token = $this->$method()) { break; } } @@ -240,12 +238,16 @@ class Lexer extends Core $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; @@ -253,10 +255,10 @@ class Lexer extends Core $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. @@ -270,7 +272,7 @@ class Lexer extends Core $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.', @@ -301,7 +303,7 @@ class Lexer extends Core // 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]; } @@ -395,16 +397,17 @@ class Lexer extends Core } 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. - } + 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. } } @@ -518,7 +521,7 @@ class Lexer extends Core 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]; } @@ -539,7 +542,10 @@ class Lexer extends Core // 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. @@ -559,13 +565,16 @@ class Lexer extends Core } // 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]; } @@ -577,8 +586,12 @@ class Lexer extends Core } // 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]; } @@ -598,7 +611,10 @@ class Lexer extends Core 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. @@ -695,14 +711,16 @@ class Lexer extends Core 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; @@ -714,40 +732,43 @@ class Lexer extends Core } } 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; } @@ -761,8 +782,8 @@ class Lexer extends Core } 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; } @@ -771,9 +792,9 @@ class Lexer extends Core } $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; @@ -794,15 +815,17 @@ class Lexer extends Core 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 { @@ -813,7 +836,7 @@ class Lexer extends Core } } - if (($this->last >= $this->len) || ($this->str[$this->last] !== $quote)) { + if ($this->last >= $this->len || $this->str[$this->last] !== $quote) { $this->error( sprintf( Translator::gettext('Ending quote %1$s was expected.'), @@ -842,7 +865,7 @@ class Lexer extends Core } 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; @@ -883,7 +906,8 @@ class Lexer extends Core 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; @@ -900,7 +924,7 @@ class Lexer extends Core { $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/src/Utils/Formatter.php b/src/Utils/Formatter.php index ec23ba9..22effca 100644 --- a/src/Utils/Formatter.php +++ b/src/Utils/Formatter.php @@ -78,6 +78,10 @@ class Formatter $options['line_ending'] = $options['type'] === 'html' ? '<br/>' : "\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. @@ -379,12 +383,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 +401,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 +410,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 +438,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,7 +447,7 @@ 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; @@ -467,14 +477,13 @@ class Formatter } 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 === '('))) + 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 === ')'))) + || ($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)) + || $curr->type === Token::TYPE_DELIMITER && mb_strlen($curr->value, 'UTF-8') < 2) // A space after delimiters that are longer than 2 characters. - || ($prev->value === 'DELIMITER') + || $prev->value === 'DELIMITER' ) { $ret .= ' '; } @@ -524,8 +533,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 +635,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; } diff --git a/tests/Parser/SelectStatementTest.php b/tests/Parser/SelectStatementTest.php index bbfc926..79440a1 100644 --- a/tests/Parser/SelectStatementTest.php +++ b/tests/Parser/SelectStatementTest.php @@ -35,7 +35,9 @@ class SelectStatementTest extends TestCase array('parser/parseSelect8'), array('parser/parseSelect9'), array('parser/parseSelect10'), + array('parser/parseSelect11'), array('parser/parseSelectErr1'), + array('parser/parseSelectErr2'), array('parser/parseSelectNested'), array('parser/parseSelectCase1'), array('parser/parseSelectCase2'), diff --git a/tests/bootstrap.php b/tests/TestCase.php index d37e4c4..ab0ecbc 100644 --- a/tests/bootstrap.php +++ b/tests/TestCase.php @@ -8,8 +8,7 @@ namespace SqlParser\Tests; use SqlParser\Lexer; use SqlParser\Parser; - -require_once 'vendor/autoload.php'; +use SqlParser\TokensList; $GLOBALS['lang'] = 'en'; diff --git a/tests/Utils/CLITest.php b/tests/Utils/CLITest.php index 3f5796d..a97d98f 100644 --- a/tests/Utils/CLITest.php +++ b/tests/Utils/CLITest.php @@ -29,17 +29,17 @@ class CLITest extends TestCase return array( array( array('q' => 'SELECT 1'), - "\x1b[35mSELECT\n \x1b[92m1\x1b[0m\n", + "\x1b[35mSELECT\n \x1b[92m1\x1b[0m\n", 0, ), array( array('query' => 'SELECT 1'), - "\x1b[35mSELECT\n \x1b[92m1\x1b[0m\n", + "\x1b[35mSELECT\n \x1b[92m1\x1b[0m\n", 0, ), array( array('q' => 'SELECT /* comment */ 1 /* other */', 'f' => 'text'), - "SELECT\n /* comment */ 1 /* other */\n", + "SELECT\n /* comment */ 1 /* other */\n", 0, ), array( @@ -50,7 +50,7 @@ class CLITest extends TestCase array( array('q' => 'SELECT 1', 'f' => 'html'), '<span class="sql-reserved">SELECT</span>' . '<br/>' . - ' <span class="sql-number">1</span>' . "\n", + ' <span class="sql-number">1</span>' . "\n", 0, ), array( diff --git a/tests/Utils/FormatterTest.php b/tests/Utils/FormatterTest.php index 1e03d17..15e6336 100644 --- a/tests/Utils/FormatterTest.php +++ b/tests/Utils/FormatterTest.php @@ -22,6 +22,7 @@ class FormatTest extends TestCase ->willReturn(array( 'type' => 'text', 'line_ending' => null, + 'indentation' => null, 'clause_newline' => null, 'parts_newline' => null, )); @@ -33,6 +34,7 @@ class FormatTest extends TestCase $expectedOptions = array( 'type' => 'test-type', 'line_ending' => '<br>', + 'indentation' => ' ', 'clause_newline' => null, 'parts_newline' => 0, 'formats' => $expected, @@ -244,30 +246,30 @@ class FormatTest extends TestCase array( 'SELECT 1', '<span class="sql-reserved">SELECT</span>' . '<br/>' . - ' <span class="sql-number">1</span>', + ' <span class="sql-number">1</span>', array('type' => 'html'), ), array( 'SELECT 1 # Comment', '<span class="sql-reserved">SELECT</span>' . '<br/>' . - ' <span class="sql-number">1</span> <span class="sql-comment"># Comment' . "\n" . + ' <span class="sql-number">1</span> <span class="sql-comment"># Comment' . "\n" . '</span>', array('type' => 'html'), ), array( 'SELECT HEX("1")', '<span class="sql-reserved">SELECT</span>' . '<br/>' . - ' <span class="sql-keyword">HEX</span>(<span class="sql-string">"1"</span>)', + ' <span class="sql-keyword">HEX</span>(<span class="sql-string">"1"</span>)', array('type' => 'html'), ), array( 'SELECT * FROM foo WHERE bar=1', '<span class="sql-reserved">SELECT</span>' . '<br/>' . - ' *' . '<br/>' . + ' *' . '<br/>' . '<span class="sql-reserved">FROM</span>' . '<br/>' . - ' foo' . '<br/>' . + ' foo' . '<br/>' . '<span class="sql-reserved">WHERE</span>' . '<br/>' . - ' bar = <span class="sql-number">1</span>', + ' bar = <span class="sql-number">1</span>', array('type' => 'html'), ), array( @@ -276,9 +278,9 @@ class FormatTest extends TestCase '<span class="sql-reserved">PROCEDURE</span> SPTEST()' . '<br/>' . '<span class="sql-keyword">BEGIN</span>' . '<br/>' . '<span class="sql-reserved">FROM</span>' . '<br/>' . - ' a' . '<br/>' . + ' a' . '<br/>' . '<span class="sql-reserved">SELECT</span>' . '<br/>' . - ' *;' . '<br/>' . + ' *;' . '<br/>' . '<span class="sql-keyword">END</span>', array('type' => 'html'), ), @@ -286,7 +288,7 @@ class FormatTest extends TestCase 'INSERT INTO foo VALUES (0, 0, 0), (1, 1, 1)', '<span class="sql-reserved">INSERT</span>' . '<br/>' . '<span class="sql-reserved">INTO</span>' . '<br/>' . - ' foo' . '<br/>' . + ' foo' . '<br/>' . '<span class="sql-reserved">VALUES</span>' . '(<span class="sql-number">0</span>, <span class="sql-number">0</span>, <span class="sql-number">0</span>),' . '(<span class="sql-number">1</span>, <span class="sql-number">1</span>, <span class="sql-number">1</span>)', @@ -294,38 +296,38 @@ class FormatTest extends TestCase ), array( 'SELECT 1', - "\x1b[35mSELECT\n \x1b[92m1\x1b[0m", + "\x1b[35mSELECT\n \x1b[92m1\x1b[0m", array('type' => 'cli'), ), array( 'SELECT "Text" AS BAR', - "\x1b[35mSELECT\n \x1b[91m\"Text\" \x1b[35mAS \x1b[39mBAR\x1b[0m", + "\x1b[35mSELECT\n \x1b[91m\"Text\" \x1b[35mAS \x1b[39mBAR\x1b[0m", array('type' => 'cli'), ), array( 'SELECT coditm AS Item, descripcion AS Descripcion, contenedores AS Contenedores, IF(suspendido = 1, Si, NO) AS Suspendido FROM `DW_articulos` WHERE superado = 0', '<span class="sql-reserved">SELECT</span>' . '<br/>' . - ' coditm <span class="sql-reserved">AS</span> Item,' . '<br/>' . - ' descripcion <span class="sql-reserved">AS</span> Descripcion,' . '<br/>' . - ' contenedores <span class="sql-reserved">AS</span> Contenedores,' . '<br/>' . - ' <span class="sql-reserved">IF</span>(suspendido = <span class="sql-number">1</span>, Si, <span class="sql-keyword">NO</span>) <span class="sql-reserved">AS</span> Suspendido' . '<br/>' . + ' coditm <span class="sql-reserved">AS</span> Item,' . '<br/>' . + ' descripcion <span class="sql-reserved">AS</span> Descripcion,' . '<br/>' . + ' contenedores <span class="sql-reserved">AS</span> Contenedores,' . '<br/>' . + ' <span class="sql-reserved">IF</span>(suspendido = <span class="sql-number">1</span>, Si, <span class="sql-keyword">NO</span>) <span class="sql-reserved">AS</span> Suspendido' . '<br/>' . '<span class="sql-reserved">FROM</span>' . '<br/>' . - ' <span class="sql-variable">`DW_articulos`</span>' . '<br/>' . + ' <span class="sql-variable">`DW_articulos`</span>' . '<br/>' . '<span class="sql-reserved">WHERE</span>' . '<br/>' . - ' superado = <span class="sql-number">0</span>', + ' superado = <span class="sql-number">0</span>', array('type' => 'html'), ), array( 'SELECT 1 -- comment', '<span class="sql-reserved">SELECT</span>' . '<br/>' . - ' <span class="sql-number">1</span> <span class="sql-comment">-- comment' . "\n" . + ' <span class="sql-number">1</span> <span class="sql-comment">-- comment' . "\n" . '</span>', array('type' => 'html'), ), array( 'SELECT 1 -- comment', '<span class="sql-reserved">SELECT</span>' . '<br/>' . - ' <span class="sql-number">1</span>', + ' <span class="sql-number">1</span>', array('type' => 'html', 'remove_comments' => true), ), array( @@ -337,22 +339,26 @@ class FormatTest extends TestCase ' `query` text NOT NULL,' . "\n" . ' PRIMARY KEY (`id`)' . "\n", '<span class="sql-reserved">CREATE</span> <span class="sql-reserved">TABLE</span> <span class="sql-reserved">IF NOT EXISTS</span> <span class="sql-variable">`pma__bookmark`</span>(' . '<br/>' . - ' <span class="sql-variable">`id`</span> <span class="sql-reserved">INT</span>(<span class="sql-number">11</span>) <span class="sql-reserved">NOT NULL</span> <span class="sql-keyword">AUTO_INCREMENT</span>,' . '<br/>' . - ' <span class="sql-variable">`dbase`</span> <span class="sql-reserved">VARCHAR</span>(<span class="sql-number">255</span>) <span class="sql-reserved">NOT NULL</span> <span class="sql-reserved">DEFAULT</span> <span class="sql-string">""</span>,' . '<br/>' . - ' <span class="sql-variable">`user`</span> <span class="sql-reserved">VARCHAR</span>(<span class="sql-number">255</span>) <span class="sql-reserved">NOT NULL</span> <span class="sql-reserved">DEFAULT</span> <span class="sql-string">""</span>,' . '<br/>' . - ' <span class="sql-variable">`label`</span> <span class="sql-reserved">VARCHAR</span>(<span class="sql-number">255</span>) <span class="sql-reserved">COLLATE</span> utf8_general_ci <span class="sql-reserved">NOT NULL</span> <span class="sql-reserved">DEFAULT</span> <span class="sql-string">""</span>,' . '<br/>' . - ' <span class="sql-variable">`query`</span> <span class="sql-keyword">TEXT</span> <span class="sql-reserved">NOT NULL</span>,' . '<br/>' . - ' <span class="sql-reserved">PRIMARY KEY</span>(<span class="sql-variable">`id`</span>)', + ' <span class="sql-variable">`id`</span> <span class="sql-reserved">INT</span>(<span class="sql-number">11</span>) <span class="sql-reserved">NOT NULL</span> <span class="sql-keyword">AUTO_INCREMENT</span>,' . '<br/>' . + ' <span class="sql-variable">`dbase`</span> <span class="sql-reserved">VARCHAR</span>(<span class="sql-number">255</span>) <span class="sql-reserved">NOT NULL</span> <span class="sql-reserved">DEFAULT</span> <span class="sql-string">""</span>,' . '<br/>' . + ' <span class="sql-variable">`user`</span> <span class="sql-reserved">VARCHAR</span>(<span class="sql-number">255</span>) <span class="sql-reserved">NOT NULL</span> <span class="sql-reserved">DEFAULT</span> <span class="sql-string">""</span>,' . '<br/>' . + ' <span class="sql-variable">`label`</span> <span class="sql-reserved">VARCHAR</span>(<span class="sql-number">255</span>) <span class="sql-reserved">COLLATE</span> utf8_general_ci <span class="sql-reserved">NOT NULL</span> <span class="sql-reserved">DEFAULT</span> <span class="sql-string">""</span>,' . '<br/>' . + ' <span class="sql-variable">`query`</span> <span class="sql-keyword">TEXT</span> <span class="sql-reserved">NOT NULL</span>,' . '<br/>' . + ' <span class="sql-reserved">PRIMARY KEY</span>(<span class="sql-variable">`id`</span>)', array('type' => 'html'), ), array( "select '<s>xss' from `<s>xss` , <s>nxss /*s<s>xss*/", - '<span class="sql-reserved">SELECT</span><br/> <span class="sql-string">\'<s>xss\'</span><br/><span class="sql-reserved">FROM</span><br/> <span class="sql-variable">`<s>xss`</span>,<br/> < s > nxss <span class="sql-comment">/*s<s>xss*/</span>', + '<span class="sql-reserved">SELECT</span>' . '<br/>' . + ' <span class="sql-string">\'<s>xss\'</span>' . '<br/>' . + '<span class="sql-reserved">FROM</span>' . '<br/>' . + ' <span class="sql-variable">`<s>xss`</span>,' . '<br/>' . + ' < s > nxss <span class="sql-comment">/*s<s>xss*/</span>', array('type' => 'html'), ), array( "select 'text\x1b[33mcolor-inj' from tbl", - "\x1b[35mSELECT\n \x1b[91m'text\\x1B[33mcolor-inj'\n\x1b[35mFROM\n \x1b[39mtbl\x1b[0m", + "\x1b[35mSELECT\n \x1b[91m'text\\x1B[33mcolor-inj'\n\x1b[35mFROM\n \x1b[39mtbl\x1b[0m", array('type' => 'cli'), ), ); diff --git a/tests/data/parser/parseSelect11.in b/tests/data/parser/parseSelect11.in new file mode 100644 index 0000000..99f01a8 --- /dev/null +++ b/tests/data/parser/parseSelect11.in @@ -0,0 +1 @@ +SELECT 1 AND NOT 1 diff --git a/tests/data/parser/parseSelect11.out b/tests/data/parser/parseSelect11.out new file mode 100644 index 0000000..2f08592 --- /dev/null +++ b/tests/data/parser/parseSelect11.out @@ -0,0 +1,4 @@ +a:4:{s:5:"query";s:19:"SELECT 1 AND NOT 1 +";s:5:"lexer";O:15:"SqlParser\Lexer":8:{s:6:"strict";b:0;s:3:"str";s:19:"SELECT 1 AND NOT 1 +";s:3:"len";i:19;s:4:"last";i:19;s:4:"list";O:20:"SqlParser\TokensList":3:{s:6:"tokens";a:11:{i:0;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"SELECT";s:5:"value";s:6:"SELECT";s:4:"type";i:1;s:5:"flags";i:3;s:8:"position";i:0;}i:1;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:6;}i:2;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"1";s:5:"value";i:1;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:7;}i:3;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:8;}i:4;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"AND";s:5:"value";s:3:"AND";s:4:"type";i:1;s:5:"flags";i:3;s:8:"position";i:9;}i:5;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:12;}i:6;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"NOT";s:5:"value";s:3:"NOT";s:4:"type";i:1;s:5:"flags";i:3;s:8:"position";i:13;}i:7;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:16;}i:8;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"1";s:5:"value";i:1;s:4:"type";i:6;s:5:"flags";i:0;s:8:"position";i:17;}i:9;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" +";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:18;}i:10;O:15:"SqlParser\Token":5:{s:5:"token";N;s:5:"value";N;s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";N;}}s:5:"count";i:11;s:3:"idx";i:11;}s:9:"delimiter";s:1:";";s:12:"delimiterLen";i:1;s:6:"errors";a:0:{}}s:6:"parser";O:16:"SqlParser\Parser":5:{s:4:"list";r:8;s:6:"strict";b:0;s:6:"errors";a:0:{}s:10:"statements";a:1:{i:0;O:36:"SqlParser\Statements\SelectStatement":16:{s:4:"expr";a:1:{i:0;O:31:"SqlParser\Components\Expression":7:{s:8:"database";N;s:5:"table";N;s:6:"column";N;s:4:"expr";s:11:"1 AND NOT 1";s:5:"alias";N;s:8:"function";N;s:8:"subquery";N;}}s:4:"from";a:0:{}s:9:"partition";N;s:5:"where";N;s:5:"group";N;s:6:"having";N;s:5:"order";N;s:5:"limit";N;s:9:"procedure";N;s:4:"into";N;s:4:"join";N;s:5:"union";a:0:{}s:11:"end_options";N;s:7:"options";O:33:"SqlParser\Components\OptionsArray":1:{s:7:"options";a:0:{}}s:5:"first";i:0;s:4:"last";i:9;}}s:8:"brackets";i:0;}s:6:"errors";a:2:{s:5:"lexer";a:0:{}s:6:"parser";a:0:{}}}
\ No newline at end of file diff --git a/tests/data/parser/parseSelectErr2.in b/tests/data/parser/parseSelectErr2.in new file mode 100644 index 0000000..494d5b7 --- /dev/null +++ b/tests/data/parser/parseSelectErr2.in @@ -0,0 +1 @@ +select * from foobar where foo = @ diff --git a/tests/data/parser/parseSelectErr2.out b/tests/data/parser/parseSelectErr2.out new file mode 100644 index 0000000..cece70e --- /dev/null +++ b/tests/data/parser/parseSelectErr2.out @@ -0,0 +1,4 @@ +a:4:{s:5:"query";s:35:"select * from foobar where foo = @ +";s:5:"lexer";O:15:"SqlParser\Lexer":8:{s:6:"strict";b:0;s:3:"str";s:35:"select * from foobar where foo = @ +";s:3:"len";i:35;s:4:"last";i:35;s:4:"list";O:20:"SqlParser\TokensList":3:{s:6:"tokens";a:16:{i:0;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"select";s:5:"value";s:6:"SELECT";s:4:"type";i:1;s:5:"flags";i:3;s:8:"position";i:0;}i:1;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:6;}i:2;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"*";s:5:"value";s:1:"*";s:4:"type";i:2;s:5:"flags";i:1;s:8:"position";i:7;}i:3;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:8;}i:4;O:15:"SqlParser\Token":5:{s:5:"token";s:4:"from";s:5:"value";s:4:"FROM";s:4:"type";i:1;s:5:"flags";i:3;s:8:"position";i:9;}i:5;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:13;}i:6;O:15:"SqlParser\Token":5:{s:5:"token";s:6:"foobar";s:5:"value";s:6:"foobar";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:14;}i:7;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:20;}i:8;O:15:"SqlParser\Token":5:{s:5:"token";s:5:"where";s:5:"value";s:5:"WHERE";s:4:"type";i:1;s:5:"flags";i:3;s:8:"position";i:21;}i:9;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:26;}i:10;O:15:"SqlParser\Token":5:{s:5:"token";s:3:"foo";s:5:"value";s:3:"foo";s:4:"type";i:0;s:5:"flags";i:0;s:8:"position";i:27;}i:11;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:30;}i:12;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"=";s:5:"value";s:1:"=";s:4:"type";i:2;s:5:"flags";i:2;s:8:"position";i:31;}i:13;O:15:"SqlParser\Token":5:{s:5:"token";s:1:" ";s:5:"value";s:1:" ";s:4:"type";i:3;s:5:"flags";i:0;s:8:"position";i:32;}i:14;O:15:"SqlParser\Token":5:{s:5:"token";s:1:"@";s:5:"value";s:0:"";s:4:"type";i:8;s:5:"flags";i:1;s:8:"position";i:33;}i:15;O:15:"SqlParser\Token":5:{s:5:"token";N;s:5:"value";N;s:4:"type";i:9;s:5:"flags";i:0;s:8:"position";N;}}s:5:"count";i:16;s:3:"idx";i:16;}s:9:"delimiter";s:1:";";s:12:"delimiterLen";i:1;s:6:"errors";a:0:{}}s:6:"parser";O:16:"SqlParser\Parser":5:{s:4:"list";r:8;s:6:"strict";b:0;s:6:"errors";a:0:{}s:10:"statements";a:1:{i:0;O:36:"SqlParser\Statements\SelectStatement":16:{s:4:"expr";a:1:{i:0;O:31:"SqlParser\Components\Expression":7:{s:8:"database";N;s:5:"table";N;s:6:"column";N;s:4:"expr";s:1:"*";s:5:"alias";N;s:8:"function";N;s:8:"subquery";N;}}s:4:"from";a:1:{i:0;O:31:"SqlParser\Components\Expression":7:{s:8:"database";N;s:5:"table";s:6:"foobar";s:6:"column";N;s:4:"expr";s:6:"foobar";s:5:"alias";N;s:8:"function";N;s:8:"subquery";N;}}s:9:"partition";N;s:5:"where";a:1:{i:0;O:30:"SqlParser\Components\Condition":3:{s:11:"identifiers";a:2:{i:0;s:3:"foo";i:1;s:0:"";}s:10:"isOperator";b:0;s:4:"expr";s:7:"foo = @";}}s:5:"group";N;s:6:"having";N;s:5:"order";N;s:5:"limit";N;s:9:"procedure";N;s:4:"into";N;s:4:"join";N;s:5:"union";a:0:{}s:11:"end_options";N;s:7:"options";O:33:"SqlParser\Components\OptionsArray":1:{s:7:"options";a:0:{}}s:5:"first";i:0;s:4:"last";i:14;}}s:8:"brackets";i:0;}s:6:"errors";a:2:{s:5:"lexer";a:1:{i:0;a:4:{i:0;s:27:"Variable name was expected.";i:1;s:1:" +";i:2;i:34;i:3;i:0;}}s:6:"parser";a:0:{}}}
\ No newline at end of file diff --git a/tools/ContextGenerator.php b/tools/ContextGenerator.php index 5876b86..43ea315 100644 --- a/tools/ContextGenerator.php +++ b/tools/ContextGenerator.php @@ -193,7 +193,7 @@ class ContextGenerator if ($i == 0) { $ret .= str_repeat(' ', $spaces); } - $ret .= "'" . $word . "' => " . $type . ', '; + $ret .= sprintf('\'%s\' => %s, ', $word, $type); if (++$i == $count) { $ret .= "\n"; $i = 0; |