Merge branch 'QA_4_6' of github.com:phpmyadmin/phpmyadmin into QA_4_6

Conflicts:
	ChangeLog
This commit is contained in:
Madhura Jayaratne 2016-02-11 21:05:43 +11:00
commit 3a054c82d0
16 changed files with 279 additions and 208 deletions

View File

@ -52,6 +52,13 @@ phpMyAdmin - ChangeLog
- issue #11834 Adjust privileges fails if database name contains underscores
- issue #11906 'Loading...' banner shows on login screen
- issue #11930 Fixed changing of table parameters, eg. AUTO_INCREMENT
- issue #11885 Call to undefined function SqlParser\ctype_alnum()
- issue #11879 4.5.3.1 - NOW() function not recognized by parser
- issue #11867 Gracefully handle the DESC statement
- issue #11843 Fractional timestamp causes corrupted SQL export
- issue #11836 Static analysis error for valid WHERE condition with IF keyword
- issue #11800 Syntax Verifier error using REGEXP in SQL statement
- issue #11799 Backslashes in comments are being interpreted as escape characters
- issue #11909 Can't insert row into table that contains generated column
4.5.4.1 (2016-01-29)

View File

@ -63,20 +63,32 @@ class ArrayObj extends Component
$ret = empty($options['type']) ? new ArrayObj() : array();
/**
* The state of the parser.
* The last raw expression.
*
* Below are the states of the parser.
*
* 0 -----------------------[ ( ]------------------------> 1
*
* 1 ------------------[ array element ]-----------------> 2
*
* 2 ------------------------[ , ]-----------------------> 1
* 2 ------------------------[ ) ]-----------------------> (END)
*
* @var int $state
* @var string $lastRaw
*/
$state = 0;
$lastRaw = '';
/**
* The last value.
*
* @var string $lastValue
*/
$lastValue = '';
/**
* Counts brackets.
*
* @var int $brackets
*/
$brackets = 0;
/**
* Last separator (bracket or comma).
*
* @var boolean $isCommaLast
*/
$isCommaLast = false;
for (; $list->idx < $list->count; ++$list->idx) {
/**
@ -92,49 +104,72 @@ class ArrayObj extends Component
}
// Skipping whitespaces and comments.
if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) {
if (($token->type === Token::TYPE_WHITESPACE)
|| ($token->type === Token::TYPE_COMMENT)
) {
$lastRaw .= $token->token;
$lastValue = trim($lastValue) .' ';
continue;
}
if ($state === 0) {
if (($token->type !== Token::TYPE_OPERATOR) || ($token->value !== '(')) {
$parser->error(
__('An opening bracket was expected.'),
$token
);
break;
}
$state = 1;
} elseif ($state === 1) {
if (($token->type === Token::TYPE_OPERATOR) && ($token->value === ')')) {
// Empty array.
break;
}
if (empty($options['type'])) {
$ret->values[] = $token->value;
$ret->raw[] = $token->token;
} else {
$ret[] = $options['type']::parse(
$parser,
$list,
empty($options['typeOptions']) ? array() : $options['typeOptions']
);
}
$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
);
break;
}
if ($token->value === ',') {
$state = 1;
} else { // )
break;
if (($brackets === 0)
&& (($token->type !== Token::TYPE_OPERATOR)
|| ($token->value !== '('))
) {
$parser->error(__('An opening bracket was expected.'), $token);
break;
}
if ($token->type === Token::TYPE_OPERATOR) {
if ($token->value === '(') {
if (++$brackets === 1) { // 1 is the base level.
continue;
}
} elseif ($token->value === ')') {
if (--$brackets === 0) { // Array ended.
break;
}
} elseif ($token->value === ',') {
if ($brackets === 1) {
$isCommaLast = true;
if (empty($options['type'])) {
$ret->raw[] = trim($lastRaw);
$ret->values[] = trim($lastValue);
$lastRaw = $lastValue = '';
}
}
continue;
}
}
if (empty($options['type'])) {
$lastRaw .= $token->token;
$lastValue .= $token->value;
} else {
$ret[] = $options['type']::parse(
$parser,
$list,
empty($options['typeOptions']) ? array() : $options['typeOptions']
);
}
}
// Handling last element.
//
// This is treated differently to treat the following cases:
//
// => array()
// (,) => array('', '')
// () => array()
// (a,) => array('a', '')
// (a) => array('a')
//
$lastRaw = trim($lastRaw);
if ((empty($options['type']))
&& ((strlen($lastRaw) > 0) || ($isCommaLast))
) {
$ret->raw[] = $lastRaw;
$ret->values[] = trim($lastValue);
}
return $ret;

View File

@ -30,17 +30,18 @@ class Condition extends Component
*
* @var array
*/
public static $DELIMITERS = array('&&', 'AND', 'OR', 'XOR', '||');
public static $DELIMITERS = array('&&', '||', 'AND', 'OR', 'XOR');
/**
* Hash map containing reserved keywords that are also operators.
* List of allowed reserved keywords in conditions.
*
* @var array
*/
public static $OPERATORS = array(
public static $ALLOWED_KEYWORDS = array(
'AND' => 1,
'BETWEEN' => 1,
'EXISTS' => 1,
'IF' => 1,
'IN' => 1,
'IS' => 1,
'LIKE' => 1,
@ -106,6 +107,7 @@ class Condition extends Component
/**
* Whether there was a `BETWEEN` keyword before or not.
*
* It is required to keep track of them because their structure contains
* the keyword `AND`, which is also an operator that delimits
* expressions.
@ -115,6 +117,7 @@ class Condition extends Component
$betweenBefore = false;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
*
@ -142,11 +145,12 @@ class Condition extends Component
// Conditions are delimited by logical operators.
if (in_array($token->value, static::$DELIMITERS, true)) {
if (($betweenBefore) && ($token->value === 'AND')) {
// The syntax of keyword `BETWEEN` is hard-coded.
$betweenBefore = false;
} else {
// The expression ended.
$expr->expr = trim($expr->expr);
if (!empty($expr->expr)) {
// Adding the condition that is delimited by this operator.
$ret[] = $expr;
}
@ -155,11 +159,21 @@ class Condition extends Component
$expr->isOperator = true;
$ret[] = $expr;
// Preparing to parse another condition.
$expr = new Condition();
continue;
}
}
if (($token->type === Token::TYPE_KEYWORD) && ($token->flags & Token::FLAG_KEYWORD_RESERVED)) {
if ($token->value === 'BETWEEN') {
$betweenBefore = true;
}
if (($brackets === 0) && (empty(static::$ALLOWED_KEYWORDS[$token->value]))) {
break;
}
}
if ($token->type === Token::TYPE_OPERATOR) {
if ($token->value === '(') {
++$brackets;
@ -168,23 +182,16 @@ class Condition extends Component
}
}
// No keyword is expected.
if (($token->type === Token::TYPE_KEYWORD) && ($token->flags & Token::FLAG_KEYWORD_RESERVED)) {
if ($token->value === 'BETWEEN') {
$betweenBefore = true;
}
if (($brackets === 0) && (empty(static::$OPERATORS[$token->value]))) {
break;
}
}
$expr->expr .= $token->token;
if (($token->type === Token::TYPE_NONE)
|| (($token->type === Token::TYPE_KEYWORD) && (!($token->flags & Token::FLAG_KEYWORD_RESERVED)))
|| (($token->type === Token::TYPE_KEYWORD)
&& (!($token->flags & Token::FLAG_KEYWORD_RESERVED)))
|| ($token->type === Token::TYPE_STRING)
|| ($token->type === Token::TYPE_SYMBOL)
) {
$expr->identifiers[] = $token->value;
if (!in_array($token->value, $expr->identifiers)) {
$expr->identifiers[] = $token->value;
}
}
}

View File

@ -43,7 +43,7 @@ class CreateDefinition extends Component
'NOT NULL' => 1,
'NULL' => 1,
'DEFAULT' => array(2, 'var'),
'DEFAULT' => array(2, 'expr'),
'AUTO_INCREMENT' => 3,
'PRIMARY' => 4,
'PRIMARY KEY' => 4,

View File

@ -28,6 +28,15 @@ use SqlParser\TokensList;
class Expression extends Component
{
/**
* List of allowed reserved keywords in expressions.
*
* @var array
*/
private static $ALLOWED_KEYWORDS = array(
'AS' => 1, 'DUAL' => 1, 'NULL' => 1, 'REGEXP' => 1
);
/**
* The name of this database.
*
@ -137,9 +146,9 @@ class Expression extends Component
/**
* Whether an alias is expected. Is 2 if `AS` keyword was found.
*
* @var int $alias
* @var bool $alias
*/
$alias = 0;
$alias = false;
/**
* Counts brackets.
@ -149,17 +158,14 @@ class Expression extends Component
$brackets = 0;
/**
* Keeps track of the previous token.
* Possible values:
* string, if function was previously found;
* true, if opening bracket was previously found;
* null, in any other case.
* Keeps track of the last two previous tokens.
*
* @var string|bool $prev
* @var Token[] $prev
*/
$prev = null;
$prev = array(null, null);
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
*
@ -173,36 +179,48 @@ class Expression extends Component
}
// Skipping whitespaces and comments.
if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) {
if (($isExpr) && (!$alias)) {
if (($token->type === Token::TYPE_WHITESPACE)
|| ($token->type === Token::TYPE_COMMENT)
) {
if ($isExpr) {
$ret->expr .= $token->token;
}
if (($alias === 0) && (empty($options['noAlias'])) && (!$isExpr) && (!$dot) && (!empty($ret->expr))) {
$alias = 1;
}
continue;
}
if (($token->type === Token::TYPE_KEYWORD)
&& ($token->flags & Token::FLAG_KEYWORD_RESERVED)
&& ($token->value !== 'DUAL')
&& ($token->value !== 'NULL')
) {
// Keywords may be found only between brackets.
if ($brackets === 0) {
if ((empty($options['noAlias'])) && ($token->value === 'AS')) {
$alias = 2;
continue;
}
if (!($token->flags & Token::FLAG_KEYWORD_FUNCTION)) {
if ($token->type === Token::TYPE_KEYWORD) {
if (($brackets > 0) && (empty($ret->subquery))
&& (!empty(Parser::$STATEMENT_PARSERS[$token->value]))
) {
// A `(` was previously found and this keyword is the
// beginning of a statement, so this is a subquery.
$ret->subquery = $token->value;
} elseif ($token->flags & Token::FLAG_KEYWORD_FUNCTION) {
$isExpr = true;
} elseif (($token->flags & Token::FLAG_KEYWORD_RESERVED)
&& ($brackets === 0)
) {
if (empty(self::$ALLOWED_KEYWORDS[$token->value])) {
// A reserved keyword that is not allowed in the
// expression was found so the expression must have
// ended and a new clause is starting.
break;
}
} elseif ($prev === true) {
if ((empty($ret->subquery) && (!empty(Parser::$STATEMENT_PARSERS[$token->value])))) {
// A `(` was previously found and this keyword is the
// beginning of a statement, so this is a subquery.
$ret->subquery = $token->value;
if ($token->value === 'AS') {
if (!empty($options['noAlias'])) {
break;
}
if (!empty($ret->alias)) {
$parser->error(
__('An alias was previously found.'),
$token
);
break;
}
$alias = true;
continue;
}
$isExpr = true;
}
}
@ -210,22 +228,25 @@ class Expression extends Component
if ((!empty($options['noBrackets']))
&& (($token->value === '(') || ($token->value === ')'))
) {
// No brackets were expected.
break;
}
if ($token->value === '(') {
++$brackets;
if ((empty($ret->function)) && ($prev !== null) && ($prev !== true)) {
// A function name was previously found and now an open
// bracket, so this is a function call.
$ret->function = $prev;
if ((empty($ret->function)) && ($prev[1] !== null)
&& (($prev[1]->type === Token::TYPE_NONE)
|| ($prev[1]->type === Token::TYPE_SYMBOL)
|| (($prev[1]->type === Token::TYPE_KEYWORD)
&& ($prev[1]->flags & Token::FLAG_KEYWORD_FUNCTION)))
) {
$ret->function = $prev[1]->value;
}
$isExpr = true;
} elseif ($token->value === ')') {
--$brackets;
if ($brackets === 0) {
if (!empty($options['bracketsDelimited'])) {
// The current token is the last brackets, the next
// one will be outside.
// The current token is the last bracket, the next
// one will be outside the expression.
$ret->expr .= $token->token;
++$list->idx;
break;
@ -236,109 +257,100 @@ class Expression extends Component
break;
}
} elseif ($token->value === ',') {
// Expressions are comma-delimited.
if ($brackets === 0) {
break;
}
}
}
if (($token->type === Token::TYPE_NUMBER) || ($token->type === Token::TYPE_BOOL)
|| (($token->type === Token::TYPE_SYMBOL) && ($token->flags & Token::FLAG_SYMBOL_VARIABLE))
|| (($token->type === Token::TYPE_OPERATOR)) && ($token->value !== '.')
if (($token->type === Token::TYPE_NUMBER)
|| ($token->type === Token::TYPE_BOOL)
|| (($token->type === Token::TYPE_SYMBOL)
&& ($token->flags & Token::FLAG_SYMBOL_VARIABLE))
|| (($token->type === Token::TYPE_OPERATOR)
&& ($token->value !== '.'))
) {
// Numbers, booleans and operators are usually part of expressions.
// Numbers, booleans and operators (except dot) are usually part
// of expressions.
$isExpr = true;
}
// Saving the previous token.
$prev[0] = $prev[1];
$prev[1] = $token;
if ($alias) {
// An alias is expected (the keyword `AS` was previously found).
if (!empty($ret->alias)) {
$parser->error(__('An alias was previously found.'), $token);
break;
}
$ret->alias = $token->value;
$alias = 0;
} else {
if (!$isExpr) {
if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '.')) {
// Found a `.` which means we expect a column name and
// 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);
}
$ret->database = $ret->table;
$ret->table = $ret->column;
$ret->column = null;
$dot = true;
} else {
// We found the name of a column (or table if column
// field should be skipped; used to parse table names).
$field = (!empty($options['skipColumn'])) ? 'table' : 'column';
if (!empty($ret->$field)) {
// No alias is expected.
if (!empty($options['noAlias'])) {
break;
}
// Parsing aliases without `AS` keyword and any
// whitespace.
// Example: SELECT 1`foo`
if (($token->type === Token::TYPE_STRING)
|| (($token->type === Token::TYPE_SYMBOL)
&& ($token->flags & Token::FLAG_SYMBOL_BACKTICK))
) {
if (!empty($ret->alias)) {
$parser->error(
__('An alias was previously found.'),
$token
);
}
$ret->alias = $token->value;
}
} else {
$ret->$field = $token->value;
}
$dot = false;
$alias = false;
} elseif ($isExpr) {
// Handling aliases.
if (/* (empty($ret->alias)) && */ ($brackets === 0)
&& (($prev[0] === null)
|| ((($prev[0]->type !== Token::TYPE_OPERATOR)
|| ($prev[0]->token === ')'))
&& (($prev[0]->type !== Token::TYPE_KEYWORD)
|| (!($prev[0]->flags & Token::FLAG_KEYWORD_RESERVED)))))
&& (($prev[1]->type === Token::TYPE_STRING)
|| (($prev[1]->type === Token::TYPE_SYMBOL)
&& (!($prev[1]->flags & Token::FLAG_SYMBOL_VARIABLE)))
|| ($prev[1]->type === Token::TYPE_NONE))
) {
if (!empty($ret->alias)) {
$parser->error(__('An alias was previously found.'), $token);
break;
}
$ret->alias = $prev[1]->value;
} else {
// Parsing aliases without `AS` keyword.
// Example: SELECT 'foo' `bar`
if (($brackets === 0) && (empty($options['noAlias']))) {
if (($token->type === Token::TYPE_NONE) || ($token->type === Token::TYPE_STRING)
|| (($token->type === Token::TYPE_SYMBOL) && ($token->flags & Token::FLAG_SYMBOL_BACKTICK))
) {
if (!empty($ret->alias)) {
$parser->error(
__('An alias was previously found.'),
$token
);
}
$ret->alias = $token->value;
continue;
$ret->expr .= $token->token;
}
} elseif (!$isExpr) {
if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '.')) {
// Found a `.` which means we expect a column name and
// 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);
}
$ret->database = $ret->table;
$ret->table = $ret->column;
$ret->column = null;
$dot = true;
$ret->expr .= $token->token;
} else {
$field = (!empty($options['skipColumn'])) ? 'table' : 'column';
if (empty($ret->$field)) {
$ret->$field = $token->value;
$ret->expr .= $token->token;
$dot = false;
} else {
// No alias is expected.
if (!empty($options['noAlias'])) {
break;
}
if (!empty($ret->alias)) {
$parser->error(__('An alias was previously found.'), $token);
break;
}
$ret->alias = $token->value;
}
}
$ret->expr .= $token->token;
}
if (($token->type === Token::TYPE_KEYWORD) && ($token->flags & Token::FLAG_KEYWORD_FUNCTION)) {
$prev = strtoupper($token->value);
} elseif (($token->type === Token::TYPE_OPERATOR) || ($token->value === '(')) {
$prev = true;
} else {
$prev = null;
}
}
if ($alias === 2) {
if ($alias) {
$parser->error(
__('An alias was expected.'),
$list->tokens[$list->idx - 1]
);
}
// Whitespaces might be added at the end.
// White-spaces might be added at the end.
$ret->expr = trim($ret->expr);
if (empty($ret->expr)) {

View File

@ -92,7 +92,7 @@ class SetOperation extends Component
if ($state === 0) {
if ($token->token === '=') {
$state = 1;
} else if ($token->value !== ',') {
} elseif ($token->value !== ',') {
$expr->column .= $token->token;
}
} elseif ($state === 1) {

View File

@ -107,7 +107,7 @@ abstract class Context
':=' => 8,
// @see Token::FLAG_OPERATOR_SQL
'(' => 16, ')' => 16, '.' => 16, ',' => 16,
'(' => 16, ')' => 16, '.' => 16, ',' => 16, ';' => 16,
);
/**
@ -397,9 +397,12 @@ abstract class Context
*/
public static function isSeparator($str)
{
// NOTES: Only ASCII characters may be separators.
// NOTES: Only non alphanumeric ASCII characters may be separators.
// `~` is the last printable ASCII character.
return ($str <= '~') && (!ctype_alnum($str)) && ($str !== '_');
return ($str <= '~') && ($str !== '_')
&& (($str < '0') || ($str > '9'))
&& (($str < 'a') || ($str > 'z'))
&& (($str < 'A') || ($str > 'Z'));
}
/**

View File

@ -37,8 +37,8 @@ class ContextMySql50000 extends Context
public static $KEYWORDS = array(
'DO' => 1, 'IO' => 1, 'NO' => 1, 'XA' => 1,
'ANY' => 1, 'BDB' => 1, 'BIT' => 1, 'CPU' => 1, 'END' => 1, 'IPC' => 1,
'NDB' => 1, 'NEW' => 1, 'ONE' => 1, 'ROW' => 1,
'ANY' => 1, 'BDB' => 1, 'CPU' => 1, 'END' => 1, 'IPC' => 1, 'NDB' => 1,
'NEW' => 1, 'ONE' => 1, 'ROW' => 1,
'BOOL' => 1, 'BYTE' => 1, 'CODE' => 1, 'CUBE' => 1, 'DATA' => 1, 'FAST' => 1,
'FILE' => 1, 'FULL' => 1, 'HASH' => 1, 'HELP' => 1, 'LAST' => 1, 'LOGS' => 1,
'MODE' => 1, 'NAME' => 1, 'NEXT' => 1, 'NONE' => 1, 'OPEN' => 1, 'PAGE' => 1,
@ -162,7 +162,7 @@ class ContextMySql50000 extends Context
'DEFAULT CHARACTER SET' => 7,
'WITH CONSISTENT SNAPSHOT' => 7,
'XML' => 9,
'BIT' => 9, 'XML' => 9,
'ENUM' => 9, 'JSON' => 9, 'TEXT' => 9,
'ARRAY' => 9,
'SERIAL' => 9,

View File

@ -37,8 +37,8 @@ class ContextMySql50100 extends Context
public static $KEYWORDS = array(
'AT' => 1, 'DO' => 1, 'IO' => 1, 'NO' => 1, 'XA' => 1,
'ANY' => 1, 'BDB' => 1, 'BIT' => 1, 'CPU' => 1, 'END' => 1, 'IPC' => 1,
'NDB' => 1, 'NEW' => 1, 'ONE' => 1, 'ROW' => 1,
'ANY' => 1, 'BDB' => 1, 'CPU' => 1, 'END' => 1, 'IPC' => 1, 'NDB' => 1,
'NEW' => 1, 'ONE' => 1, 'ROW' => 1,
'BOOL' => 1, 'BYTE' => 1, 'CODE' => 1, 'CUBE' => 1, 'DATA' => 1, 'DISK' => 1,
'ENDS' => 1, 'FAST' => 1, 'FILE' => 1, 'FULL' => 1, 'GOTO' => 1, 'HASH' => 1,
'HELP' => 1, 'HOST' => 1, 'LAST' => 1, 'LESS' => 1, 'LIST' => 1, 'LOGS' => 1,
@ -175,7 +175,7 @@ class ContextMySql50100 extends Context
'DEFAULT CHARACTER SET' => 7,
'WITH CONSISTENT SNAPSHOT' => 7,
'XML' => 9,
'BIT' => 9, 'XML' => 9,
'ENUM' => 9, 'JSON' => 9, 'TEXT' => 9,
'ARRAY' => 9,
'SERIAL' => 9,

View File

@ -37,8 +37,8 @@ class ContextMySql50500 extends Context
public static $KEYWORDS = array(
'AT' => 1, 'DO' => 1, 'IO' => 1, 'NO' => 1, 'XA' => 1,
'ANY' => 1, 'BIT' => 1, 'CPU' => 1, 'END' => 1, 'IPC' => 1, 'NDB' => 1,
'NEW' => 1, 'ONE' => 1, 'ROW' => 1,
'ANY' => 1, 'CPU' => 1, 'END' => 1, 'IPC' => 1, 'NDB' => 1, 'NEW' => 1,
'ONE' => 1, 'ROW' => 1,
'BOOL' => 1, 'BYTE' => 1, 'CODE' => 1, 'CUBE' => 1, 'DATA' => 1, 'DISK' => 1,
'ENDS' => 1, 'FAST' => 1, 'FILE' => 1, 'FULL' => 1, 'HASH' => 1, 'HELP' => 1,
'HOST' => 1, 'LAST' => 1, 'LESS' => 1, 'LIST' => 1, 'LOGS' => 1, 'MODE' => 1,
@ -180,7 +180,7 @@ class ContextMySql50500 extends Context
'DEFAULT CHARACTER SET' => 7,
'WITH CONSISTENT SNAPSHOT' => 7,
'XML' => 9,
'BIT' => 9, 'XML' => 9,
'ENUM' => 9, 'JSON' => 9, 'TEXT' => 9,
'ARRAY' => 9,
'SERIAL' => 9,

View File

@ -37,8 +37,8 @@ class ContextMySql50600 extends Context
public static $KEYWORDS = array(
'AT' => 1, 'DO' => 1, 'IO' => 1, 'NO' => 1, 'XA' => 1,
'ANY' => 1, 'BIT' => 1, 'CPU' => 1, 'END' => 1, 'IPC' => 1, 'NDB' => 1,
'NEW' => 1, 'ONE' => 1, 'ROW' => 1,
'ANY' => 1, 'CPU' => 1, 'END' => 1, 'IPC' => 1, 'NDB' => 1, 'NEW' => 1,
'ONE' => 1, 'ROW' => 1,
'BOOL' => 1, 'BYTE' => 1, 'CODE' => 1, 'CUBE' => 1, 'DATA' => 1, 'DISK' => 1,
'ENDS' => 1, 'FAST' => 1, 'FILE' => 1, 'FULL' => 1, 'HASH' => 1, 'HELP' => 1,
'HOST' => 1, 'LAST' => 1, 'LESS' => 1, 'LIST' => 1, 'LOGS' => 1, 'MODE' => 1,
@ -186,7 +186,7 @@ class ContextMySql50600 extends Context
'DEFAULT CHARACTER SET' => 7,
'WITH CONSISTENT SNAPSHOT' => 7,
'XML' => 9,
'BIT' => 9, 'XML' => 9,
'ENUM' => 9, 'JSON' => 9, 'TEXT' => 9,
'ARRAY' => 9,
'SERIAL' => 9,

View File

@ -37,8 +37,8 @@ class ContextMySql50700 extends Context
public static $KEYWORDS = array(
'AT' => 1, 'DO' => 1, 'IO' => 1, 'NO' => 1, 'XA' => 1,
'ANY' => 1, 'BIT' => 1, 'CPU' => 1, 'END' => 1, 'IPC' => 1, 'NDB' => 1,
'NEW' => 1, 'ONE' => 1, 'ROW' => 1, 'XID' => 1,
'ANY' => 1, 'CPU' => 1, 'END' => 1, 'IPC' => 1, 'NDB' => 1, 'NEW' => 1,
'ONE' => 1, 'ROW' => 1, 'XID' => 1,
'BOOL' => 1, 'BYTE' => 1, 'CODE' => 1, 'CUBE' => 1, 'DATA' => 1, 'DISK' => 1,
'ENDS' => 1, 'FAST' => 1, 'FILE' => 1, 'FULL' => 1, 'HASH' => 1, 'HELP' => 1,
'HOST' => 1, 'LAST' => 1, 'LESS' => 1, 'LIST' => 1, 'LOGS' => 1, 'MODE' => 1,
@ -193,7 +193,7 @@ class ContextMySql50700 extends Context
'DEFAULT CHARACTER SET' => 7,
'WITH CONSISTENT SNAPSHOT' => 7,
'XML' => 9,
'BIT' => 9, 'XML' => 9,
'ENUM' => 9, 'JSON' => 9, 'TEXT' => 9,
'ARRAY' => 9,
'SERIAL' => 9,

View File

@ -36,6 +36,7 @@ class Parser
// MySQL Utility Statements
'DESCRIBE' => 'SqlParser\\Statements\\ExplainStatement',
'DESC' => 'SqlParser\\Statements\\ExplainStatement',
'EXPLAIN' => 'SqlParser\\Statements\\ExplainStatement',
'FLUSH' => '',
'GRANT' => '',

View File

@ -76,8 +76,8 @@ class AlterStatement extends Statement
$parser,
$list,
array(
'noAlias' => true,
'noBrackets' => true,
'noAlias' => true,
'noBrackets' => true,
)
);
++$list->idx; // Skipping field.

View File

@ -29,12 +29,18 @@ class BufferedQuery
{
// Constants that describe the current status of the parser.
const STATUS_STRING_SINGLE_QUOTES = 1;
const STATUS_STRING_DOUBLE_QUOTES = 2;
const STATUS_STRING_BACKTICK = 3;
const STATUS_COMMENT_BASH = 4;
const STATUS_COMMENT_C = 5;
const STATUS_COMMENT_SQL = 6;
// A string is being parsed.
const STATUS_STRING = 16; // 0001 0000
const STATUS_STRING_SINGLE_QUOTES = 17; // 0001 0001
const STATUS_STRING_DOUBLE_QUOTES = 18; // 0001 0010
const STATUS_STRING_BACKTICK = 20; // 0001 0100
// A comment is being parsed.
const STATUS_COMMENT = 32; // 0010 0000
const STATUS_COMMENT_BASH = 33; // 0010 0001
const STATUS_COMMENT_C = 34; // 0010 0010
const STATUS_COMMENT_SQL = 36; // 0010 0100
/**
* The query that is being processed.
@ -193,7 +199,7 @@ class BufferedQuery
* treated differently, because of the preceding backslash, it will
* be ignored.
*/
if ($this->query[$i] === '\\') {
if (($this->status & static::STATUS_COMMENT == 0) && ($this->query[$i] === '\\')) {
$this->current .= $this->query[$i] . $this->query[++$i];
continue;
}

View File

@ -305,8 +305,8 @@ class Formatter
&& (!$formattedOptions)
&& (empty(self::$INLINE_CLAUSES[$lastClause]))
&& (($curr->type !== Token::TYPE_KEYWORD)
|| (($curr->type === Token::TYPE_KEYWORD)
&& ($curr->flags & Token::FLAG_KEYWORD_FUNCTION)))
|| (($curr->type === Token::TYPE_KEYWORD)
&& ($curr->flags & Token::FLAG_KEYWORD_FUNCTION)))
) {
$formattedOptions = true;
$lineEnded = true;