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));
}
}