Fix failing tests.

Updated sql-parser library.

Signed-off-by: Dan Ungureanu <udan1107@gmail.com>
This commit is contained in:
Dan Ungureanu 2015-06-30 01:06:44 +03:00
parent 46e44ed0d5
commit d2b5498565
36 changed files with 566 additions and 248 deletions

View File

@ -28,8 +28,7 @@ extract($analyzed_sql_results);
if (!empty($analyzed_sql_results['select_tables'])) {
// Previous table and database name is stored to check if it changed.
$prevDb = $db;
$prevTable = $table;
$prev_db = $db;
if (count($analyzed_sql_results['select_tables']) > 1) {
@ -50,9 +49,8 @@ if (!empty($analyzed_sql_results['select_tables'])) {
// There is no point checking if a reload is required if we already decided
// to reload. Also, no reload is required for AJAX requests.
if ((empty($reload)) && (empty($GLOBALS['is_ajax_request']))) {
// NOTE: Tables are case-insensitive.
$reload = ((strcasecmp($db, $prevDb) == 0)
|| (strcasecmp($table, $prevTable)) == 0) ? false : true;
// NOTE: Database names are case-insensitive.
$reload = strcasecmp($db, $prev_db) != 0;
}
// Updating the array as well.

View File

@ -0,0 +1,84 @@
<?php
/**
* This is one of the most important components, along with the lexer and the
* parser.
*
* @package SqlParser
*/
namespace SqlParser;
use SqlParser\Exceptions\ParserException;
/**
* Builds the string representation of a Statement.
*
* @category Parser
* @package SqlParser
* @author Dan Ungureanu <udan1107@gmail.com>
* @license http://opensource.org/licenses/GPL-2.0 GNU Public License
*/
class Builder
{
/**
* Statement to be build.
*
* @var Statement
*/
public $statement;
/**
* Built query.
*
* @var string
*/
public $query;
/**
* Constructor.
*
* @param Statement $statement
*/
public function __construct($statement = null)
{
$this->statement = $statement;
if ($this->statement != null) {
$this->build();
}
}
/**
* Builds the statement.
*
* @return void
*/
public function build()
{
$statement = $this->statement;
foreach ($statement::$CLAUSES as $clause) {
$name = $clause[0];
$type = $clause[1];
if (empty(Parser::$KEYWORD_PARSERS[$name])) {
continue;
}
$class = Parser::$KEYWORD_PARSERS[$name]['class'];
$field = Parser::$KEYWORD_PARSERS[$name]['field'];
if (empty($statement->$field)) {
continue;
}
if ($type & 2) {
$this->query .= $name . ' ';
}
if ($type & 1) {
$this->query .= $class::build($statement->$field) . ' ';
}
}
}
}

View File

@ -38,4 +38,15 @@ abstract class Fragment
return null;
}
/**
* Builds the string representation of a fragment of this type.
*
* @param Fragment $fragment The fragment to be built.
*
* @return string
*/
public static function build(Fragment $fragment)
{
return null;
}
}

View File

@ -25,6 +25,13 @@ use SqlParser\TokensList;
class ArrayFragment extends Fragment
{
/**
* The array that contains the unprocessed value of each token.
*
* @var array
*/
public $raw = array();
/**
* The array that contains the processed value of each token.
*
@ -33,11 +40,16 @@ class ArrayFragment extends Fragment
public $values = array();
/**
* The array that contains the unprocessed value of each token.
* Constructor.
*
* @var array
* @param array $raw The unprocessed values.
* @param array $values The processed values.
*/
public $raw = array();
public function __construct(array $raw = array(), array $values = array())
{
$this->raw = $raw;
$this->values = $values;
}
/**
* @param Parser $parser The parser that serves as context.
@ -67,7 +79,6 @@ class ArrayFragment extends Fragment
$state = 0;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
* @var Token
@ -114,4 +125,22 @@ class ArrayFragment extends Fragment
return $ret;
}
/**
* @param ArrayFragment $fragment The fragment to be built.
*
* @return string
*/
public static function build(ArrayFragment $fragment)
{
$values = array();
if (!empty($fragment->raw)) {
$values = $fragment->raw;
} else {
foreach ($fragment->values as $value) {
$values[] = $value;
}
}
return '(' . implode(', ', $values) . ')';
}
}

View File

@ -35,9 +35,25 @@ class CallKeyword extends Fragment
/**
* The list of parameters
*
* @var array
* @var ArrayFragment
*/
public $parameters = array();
public $parameters;
/**
* Constructor.
*
* @param string $name The name of the function to be called.
* @param array|ArrayFragment $parameters The parameters of this function.
*/
public function __construct($name = null, $parameters = null)
{
$this->name = $name;
if (is_array($parameters)) {
$this->parameters = new ArrayFragment($parameters);
} elseif ($parameters instanceof ArrayFragment) {
$this->parameters = $parameters;
}
}
/**
* @param Parser $parser The parser that serves as context.
@ -64,7 +80,6 @@ class CallKeyword extends Fragment
$state = 0;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
* @var Token
@ -86,7 +101,7 @@ class CallKeyword extends Fragment
$state = 1;
} elseif ($state === 1) {
if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '(')) {
$ret->parameters = ArrayFragment::parse($parser, $list)->values;
$ret->parameters = ArrayFragment::parse($parser, $list);
}
break;
}
@ -95,4 +110,14 @@ class CallKeyword extends Fragment
return $ret;
}
/**
* @param CallKeyword $fragment The fragment to be built.
*
* @return string
*/
public static function build(CallKeyword $fragment)
{
return $fragment->name . ArrayFragment::build($fragment->parameters);
}
}

View File

@ -92,7 +92,6 @@ class CreateDefFragment extends Fragment
$ret = new CreateDefFragment();
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
* @var Token

View File

@ -94,7 +94,6 @@ class DataTypeFragment extends Fragment
$state = 0;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
* @var Token

View File

@ -129,7 +129,6 @@ class FieldDefFragment extends Fragment
$state = 0;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
* @var Token
@ -176,7 +175,7 @@ class FieldDefFragment extends Fragment
--$list->idx;
}
$state = 5;
} else if ($state === 5) {
} elseif ($state === 5) {
if ((!empty($expr->type)) || (!empty($expr->key))) {
$ret[] = $expr;
}

View File

@ -74,6 +74,38 @@ class FieldFragment extends Fragment
*/
public $subquery;
/**
* Constructor.
*
* Syntax:
* new FieldFragment('expr')
* new FieldFragment('expr', 'alias')
* new FieldFragment('database', 'table', 'column')
* new FieldFragment('database', 'table', 'column', 'alias')
*
* If the database, table or column name is not required, pass an empty
* string.
*
* @param string $database The name of the database or the the expression.
* the the expression.
* @param string $table The name of the table or the alias of the expression.
* the alias of the expression.
* @param string $column The name of the column.
* @param string $alias The name of the alias.
*/
public function __construct($database = null, $table = null, $column = null, $alias = null)
{
if (($column === null) && ($alias === null)) {
$this->expr = $database; // case 1
$this->alias = $table; // case 2
} else {
$this->database = $database; // case 3
$this->table = $table; // case 3
$this->column = $column; // case 3
$this->alias = $alias; // case 4
}
}
/**
* @param Parser $parser The parser that serves as context.
* @param TokensList $list The list of tokens that are being parsed.
@ -120,7 +152,6 @@ class FieldFragment extends Fragment
$prev = null;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
* @var Token
@ -150,8 +181,10 @@ class FieldFragment extends Fragment
$alias = 2;
continue;
}
break;
} else if ($prev === true) {
if (!($token->flags & Token::FLAG_KEYWORD_FUNCTION)) {
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.
@ -236,7 +269,7 @@ class FieldFragment extends Fragment
if (($token->type === Token::TYPE_KEYWORD) && ($token->flags & Token::FLAG_KEYWORD_FUNCTION)) {
$prev = strtoupper($token->value);
} else if (($token->type === Token::TYPE_OPERATOR) || ($token->value === '(')) {
} elseif (($token->type === Token::TYPE_OPERATOR) || ($token->value === '(')) {
$prev = true;
} else {
$prev = null;
@ -258,4 +291,36 @@ class FieldFragment extends Fragment
--$list->idx;
return $ret;
}
/**
* @param FieldFragment $fragment The fragment to be built.
*
* @return string
*/
public static function build($fragment)
{
$ret = '';
if (!empty($fragment->expr)) {
$ret = $fragment->expr;
} else {
$fields = array();
if (!empty($fragment->database)) {
$fields[] = $fragment->database;
}
if (!empty($fragment->table)) {
$fields[] = $fragment->table;
}
if (!empty($fragment->column)) {
$fields[] = $fragment->column;
}
$ret = implode('.', $fields);
}
if (!empty($fragment->alias)) {
$ret .= ' AS ' . $fragment->alias;
}
return $ret;
}
}

View File

@ -1,7 +1,7 @@
<?php
/**
* `SELECT` keyword parser.
* Parses a a list of fields delimited by a single comma.
*
* @package SqlParser
* @subpackage Fragments
@ -14,7 +14,7 @@ use SqlParser\Token;
use SqlParser\TokensList;
/**
* `SELECT` keyword parser.
* Parses a a list of fields delimited by a single comma.
*
* @category Keywords
* @package SqlParser
@ -22,7 +22,7 @@ use SqlParser\TokensList;
* @author Dan Ungureanu <udan1107@gmail.com>
* @license http://opensource.org/licenses/GPL-2.0 GNU Public License
*/
class SelectKeyword extends Fragment
class FieldListFragment extends Fragment
{
/**
@ -39,7 +39,6 @@ class SelectKeyword extends Fragment
$expr = null;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
* @var Token
@ -64,7 +63,7 @@ class SelectKeyword extends Fragment
if (($token->type === Token::TYPE_OPERATOR) && ($token->value === ',')) {
$ret[] = $expr;
} else {
$expr = FieldFragment::parse($parser, $list);
$expr = FieldFragment::parse($parser, $list, $options);
if ($expr === null) {
break;
}
@ -80,4 +79,13 @@ class SelectKeyword extends Fragment
--$list->idx;
return $ret;
}
public static function build($fragment)
{
$ret = array();
foreach ($fragment as $frag) {
$ret[] = $frag::build($frag);
}
return implode($ret, ', ');
}
}

View File

@ -1,83 +0,0 @@
<?php
/**
* `FROM` keyword parser.
*
* @package SqlParser
* @subpackage Fragments
*/
namespace SqlParser\Fragments;
use SqlParser\Fragment;
use SqlParser\Parser;
use SqlParser\Token;
use SqlParser\TokensList;
/**
* `FROM` keyword parser.
*
* @category Keywords
* @package SqlParser
* @subpackage Fragments
* @author Dan Ungureanu <udan1107@gmail.com>
* @license http://opensource.org/licenses/GPL-2.0 GNU Public License
*/
class FromKeyword extends Fragment
{
/**
* @param Parser $parser The parser that serves as context.
* @param TokensList $list The list of tokens that are being parsed.
* @param array $options Parameters for parsing.
*
* @return FieldFragment[]
*/
public static function parse(Parser $parser, TokensList $list, array $options = array())
{
$ret = array();
$expr = new FieldFragment();
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
* @var Token
*/
$token = $list->tokens[$list->idx];
// End of statement.
if ($token->type === Token::TYPE_DELIMITER) {
break;
}
// Skipping whitespaces and comments.
if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) {
continue;
}
// No keyword is expected.
if (($token->type === Token::TYPE_KEYWORD) && ($token->flags & Token::FLAG_KEYWORD_RESERVED)) {
break;
}
if (($token->type === Token::TYPE_OPERATOR) && ($token->value === ',')) {
$ret[] = $expr;
} else {
$expr = FieldFragment::parse($parser, $list, array('skipColumn' => true));
if ($expr === null) {
break;
}
}
}
// Last iteration was not saved.
if ($expr !== null) {
$ret[] = $expr;
}
--$list->idx;
return $ret;
}
}

View File

@ -74,7 +74,6 @@ class IntoKeyword extends Fragment
$state = 0;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
* @var Token
@ -105,13 +104,13 @@ class IntoKeyword extends Fragment
if ($state === 0) {
$ret->name = $token->value;
$state = 1;
} else if ($state === 1) {
} elseif ($state === 1) {
if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '(')) {
$ret->fields = ArrayFragment::parse($parser, $list)->values;
++$list->idx;
}
break;
} else if ($state === 2) {
} elseif ($state === 2) {
$ret->name = $token->value;
++$list->idx;
break;

View File

@ -66,7 +66,6 @@ class JoinKeyword extends Fragment
$state = 0;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
* @var Token

View File

@ -95,7 +95,6 @@ class KeyFragment extends Fragment
$state = 0;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
* @var Token

View File

@ -39,6 +39,18 @@ class LimitKeyword extends Fragment
*/
public $rowCount;
/**
* Constructor.
*
* @param int $rowCount The row count.
* @param int $offset The offset.
*/
public function __construct($rowCount = null, $offset = null)
{
$this->rowCount = $rowCount;
$this->offset = $offset;
}
/**
* @param Parser $parser The parser that serves as context.
* @param TokensList $list The list of tokens that are being parsed.
@ -53,7 +65,6 @@ class LimitKeyword extends Fragment
$offset = false;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
* @var Token
@ -103,4 +114,18 @@ class LimitKeyword extends Fragment
--$list->idx;
return $ret;
}
/**
* @param LimitKeyword $fragment The fragment to be built.
*
* @return string
*/
public static function build($fragment)
{
if (empty($fragment->offset)) {
return $fragment->rowCount;
} else {
return $fragment->offset . ', ' . $fragment->rowCount;
}
}
}

View File

@ -32,6 +32,17 @@ class OptionsFragment extends Fragment
*/
public $options = array();
/**
* Constructor.
*
* @param array $options The array of options. Options that have a value
* must be an array with two keys 'name' and 'value'.
*/
public function __construct(array $options = array())
{
$this->options = $options;
}
/**
* @param Parser $parser The parser that serves as context.
* @param TokensList $list The list of tokens that are being parsed.
@ -59,7 +70,6 @@ class OptionsFragment extends Fragment
$brackets = 0;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
* @var Token
@ -127,6 +137,24 @@ class OptionsFragment extends Fragment
return $ret;
}
/**
* @param OptionsFragment $fragment The fragment to be built.
*
* @return string
*/
public static function build(OptionsFragment $fragment)
{
$options = array();
foreach ($fragment->options as $option) {
if (is_array($option)) {
$options[] = $option['name'] . '=' . $option['value'];
} else {
$options[] = $option;
}
}
return implode(' ', $options);
}
/**
* Checks if it has the specified option and returns it value or true.
*
@ -158,7 +186,7 @@ class OptionsFragment extends Fragment
{
if (is_array($options)) {
$this->options = array_merge_recursive($this->options, $options);
} else if ($options instanceof OptionsFragment) {
} elseif ($options instanceof OptionsFragment) {
$this->options = array_merge_recursive($this->options, $options->options);
}
}

View File

@ -67,7 +67,6 @@ class OrderKeyword extends Fragment
$state = 0;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
* @var Token
@ -90,7 +89,7 @@ class OrderKeyword extends Fragment
} elseif ($state === 1) {
if (($token->type === Token::TYPE_KEYWORD) && (($token->value === 'ASC') || ($token->value === 'DESC'))) {
$expr->type = $token->value;
} else if (($token->type === Token::TYPE_OPERATOR) && ($token->value === ',')) {
} elseif (($token->type === Token::TYPE_OPERATOR) && ($token->value === ',')) {
if (!empty($expr->field)) {
$ret[] = $expr;
}

View File

@ -80,7 +80,6 @@ class ParamDefFragment extends Fragment
$state = 0;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
* @var Token

View File

@ -84,7 +84,6 @@ class ReferencesKeyword extends Fragment
$state = 0;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
* @var Token
@ -104,10 +103,10 @@ class ReferencesKeyword extends Fragment
if ($state === 0) {
$ret->table = $token->value;
$state = 1;
} else if ($state === 1) {
} elseif ($state === 1) {
$ret->columns = ArrayFragment::parse($parser, $list)->values;
$state = 2;
} else if ($state === 2) {
} elseif ($state === 2) {
$ret->options = OptionsFragment::parse($parser, $list, static::$REFERENCES_OPTIONS);
++$list->idx;
break;

View File

@ -71,7 +71,6 @@ class RenameKeyword extends Fragment
$state = 0;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
* @var Token

View File

@ -67,7 +67,6 @@ class SetKeyword extends Fragment
$state = 0;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
* @var Token

View File

@ -65,7 +65,6 @@ class ValuesKeyword extends Fragment
$state = 0;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
* @var Token

View File

@ -46,6 +46,16 @@ class WhereKeyword extends Fragment
*/
public $condition;
/**
* Constructor.
*
* @param string $condition The condition or the operator.
*/
public function __construct($condition = null)
{
$this->condition = trim($condition);
}
/**
* @param Parser $parser The parser that serves as context.
* @param TokensList $list The list of tokens that are being parsed.
@ -57,10 +67,13 @@ class WhereKeyword extends Fragment
{
$ret = array();
$expr = new WhereKeyword();
/**
* The condition that was parsed so far.
* @var string
*/
$condition = '';
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
* @var Token
@ -73,23 +86,23 @@ class WhereKeyword extends Fragment
}
// Skipping whitespaces and comments.
if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) {
if ($token->type === Token::TYPE_COMMENT) {
continue;
}
// Conditions are delimited by logical operators.
if (in_array($token->value, static::$OPERATORS, true)) {
if (!empty($expr->condition)) {
$ret[] = $expr;
if (!empty(trim($condition))) {
// Adding the condition that is delimited by this operator.
$ret[] = new WhereKeyword($condition);
$condition = '';
}
$expr = new WhereKeyword();
// Adding the operator.
$expr = new WhereKeyword($token->value);
$expr->isOperator = true;
$expr->condition = $token->value;
$ret[] = $expr;
$expr = new WhereKeyword();
continue;
}
@ -98,16 +111,30 @@ class WhereKeyword extends Fragment
break;
}
$expr->condition .= $token->token;
$condition .= $token->token;
}
// Last iteration was not processed.
if (!empty($expr->condition)) {
$ret[] = $expr;
if (!empty(trim($condition))) {
$ret[] = new WhereKeyword($condition);
}
--$list->idx;
return $ret;
}
/**
* @param WhereKeyword $fragment The fragment to be built.
*
* @return string
*/
public static function build($fragment)
{
$conditions = array();
foreach ($fragment as $f) {
$conditions[] = $f->condition;
}
return implode(' ', $conditions);
}
}

View File

@ -168,7 +168,6 @@ class Lexer
$lastToken = null;
for ($this->last = 0, $lastIdx = 0; $this->last < $this->len; $lastIdx = ++$this->last) {
/**
* The new token.
* @var Token

View File

@ -9,6 +9,7 @@
*/
namespace SqlParser;
use SqlParser\Statements\SelectStatement;
use SqlParser\Exceptions\ParserException;
/**
@ -37,7 +38,7 @@ class Parser
'ANALYZE' => 'SqlParser\\Statements\\AnalyzeStatement',
'BACKUP' => 'SqlParser\\Statements\\BackupStatement',
'CHECK' => 'SqlParser\\Statements\\CheckStatement',
'CHECKSUM' => 'SqlParser\\Statements\\ChecsumStatement',
'CHECKSUM' => 'SqlParser\\Statements\\ChecksumStatement',
'OPTIMIZE' => 'SqlParser\\Statements\\OptimizeStatement',
'REPAIR' => 'SqlParser\\Statements\\RepairStatement',
'RESTORE' => 'SqlParser\\Statements\\RestoreStatement',
@ -79,38 +80,51 @@ class Parser
* @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\\Fragments\\OptionsFragment',
'field' => 'options',
),
'ALTER' => array(
'class' => 'SqlParser\\Fragments\\FieldFragment',
'field' => 'table',
'options' => array('skipColumn' => true),
),
'ANALYZE' => array(
'class' => 'SqlParser\\Fragments\\FromKeyword',
'class' => 'SqlParser\\Fragments\\FieldListFragment',
'field' => 'tables',
'options' => array('skipColumn' => true),
),
'BACKUP' => array(
'class' => 'SqlParser\\Fragments\\FromKeyword',
'class' => 'SqlParser\\Fragments\\FieldListFragment',
'field' => 'tables',
'options' => array('skipColumn' => true),
),
'CALL' => array(
'class' => 'SqlParser\\Fragments\\CallKeyword',
'field' => 'call',
),
'CHECK' => array(
'class' => 'SqlParser\\Fragments\\FromKeyword',
'class' => 'SqlParser\\Fragments\\FieldListFragment',
'field' => 'tables',
'options' => array('skipColumn' => true),
),
'CHECKSUM' => array(
'class' => 'SqlParser\\Fragments\\FromKeyword',
'class' => 'SqlParser\\Fragments\\FieldListFragment',
'field' => 'tables',
'options' => array('skipColumn' => true),
),
'DROP' => array(
'class' => 'SqlParser\\Fragments\\FromKeyword',
'field' => 'fields'
'class' => 'SqlParser\\Fragments\\FieldListFragment',
'field' => 'fields',
'options' => array('skipColumn' => true),
),
'FROM' => array(
'class' => 'SqlParser\\Fragments\\FromKeyword',
'class' => 'SqlParser\\Fragments\\FieldListFragment',
'field' => 'from',
'options' => array('skipColumn' => true),
),
'GROUP BY' => array(
'class' => 'SqlParser\\Fragments\\OrderKeyword',
@ -133,8 +147,9 @@ class Parser
'field' => 'limit',
),
'OPTIMIZE' => array(
'class' => 'SqlParser\\Fragments\\FromKeyword',
'class' => 'SqlParser\\Fragments\\FieldListFragment',
'field' => 'tables',
'options' => array('skipColumn' => true),
),
'ORDER BY' => array(
'class' => 'SqlParser\\Fragments\\OrderKeyword',
@ -153,24 +168,27 @@ class Parser
'field' => 'renames',
),
'REPAIR' => array(
'class' => 'SqlParser\\Fragments\\FromKeyword',
'class' => 'SqlParser\\Fragments\\FieldListFragment',
'field' => 'tables',
'options' => array('skipColumn' => true),
),
'RESTORE' => array(
'class' => 'SqlParser\\Fragments\\FromKeyword',
'class' => 'SqlParser\\Fragments\\FieldListFragment',
'field' => 'tables',
'options' => array('skipColumn' => true),
),
'SET' => array(
'class' => 'SqlParser\\Fragments\\SetKeyword',
'field' => 'set',
),
'SELECT' => array(
'class' => 'SqlParser\\Fragments\\SelectKeyword',
'class' => 'SqlParser\\Fragments\\FieldListFragment',
'field' => 'expr',
),
'UPDATE' => array(
'class' => 'SqlParser\\Fragments\\FromKeyword',
'class' => 'SqlParser\\Fragments\\FieldListFragment',
'field' => 'from',
'options' => array('skipColumn' => true),
),
'VALUE' => array(
'class' => 'SqlParser\\Fragments\\ValuesKeyword',
@ -252,6 +270,29 @@ class Parser
*/
public function parse()
{
/**
* Last parsed statement.
* @var Statement
*/
$lastStatement = null;
/**
* Whether a union is parsed or not.
* @var bool
*/
$inUnion = true;
/**
* The index of the last token from the last statement.
* @var int
*/
$prevLastIdx = -1;
/**
* The list of tokens.
* @var TokensList
*/
$list = &$this->list;
for (; $list->idx < $list->count; ++$list->idx) {
@ -268,13 +309,21 @@ class Parser
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->value . '".',
$token
);
// Skipping to the end of this statement.
$list->getNextOfType(Token::TYPE_DELIMITER);
//
$prevLastIdx = $list->idx;
continue;
}
@ -290,8 +339,32 @@ class Parser
*/
$stmt = new $class();
// 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
$stmt->first = $prevLastIdx + 1;
// Parsing the actual statement.
$stmt->parse($this, $this->list);
$this->statements[] = $stmt;
// Storing the index of the last token parsed and updating the old
// index.
$stmt->last = $list->idx;
$prevLastIdx = $list->idx;
// Finally, storing the statement.
if (($inUnion)
&& ($lastStatement instanceof SelectStatement)
&& ($stmt instanceof SelectStatement)
) {
$lastStatement->union[] = $stmt;
$inUnion = false;
} else {
$this->statements[] = $stmt;
$lastStatement = $stmt;
}
}
}

View File

@ -26,6 +26,20 @@ use SqlParser\Fragments\OptionsFragment;
abstract class Statement
{
/**
* The clauses of this statement, in order.
*
* The value attributed to each clause is used by the builder and it may
* have one of the following values:
*
* - 1 = 01 - add the clause only
* - 2 = 10 - add the keyword
* - 3 = 11 - add both the keyword and the clause
*
* @var array
*/
public static $CLAUSES;
/**
* The options of this query.
*
@ -59,8 +73,6 @@ abstract class Statement
*/
public function parse(Parser $parser, TokensList $list)
{
$this->first = $list->idx;
/**
* Whether options were parsed or not.
* For statements that do not have any options this is set to `true` by
@ -70,7 +82,6 @@ abstract class Statement
$parsedOptions = isset(static::$OPTIONS) ? false : true;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
* @var Token
@ -88,6 +99,12 @@ abstract class Statement
continue;
}
// Unions are parsed by the parser because they represent more than
// one statement.
if ($token->value === 'UNION') {
break;
}
/**
* The name of the class that is used for parsing.
* @var string
@ -124,7 +141,7 @@ abstract class Statement
);
$parsedOptions = true;
}
} else if ($class === null) {
} elseif ($class === null) {
// There is no parser for this keyword and isn't the beggining
// of a statement (so no options) either.
$parser->error(
@ -145,11 +162,11 @@ abstract class Statement
$this->after($parser, $list, $token);
}
$this->last = $list->idx--; // Go back to last used token.
--$list->idx; // Go back to last used token.
}
/**
* Function called before the token was processed.
* Function called before the token is processed.
*
* @param Parser $parser The instance that requests parsing.
* @param TokensList $list The list of tokens to be parsed.
@ -175,5 +192,4 @@ abstract class Statement
{
}
}

View File

@ -97,6 +97,8 @@ class AlterStatement extends NotImplementedStatement
);
/**
* Function called after the token was processed.
*
* Extracts the name of affected column.
*
* @param Parser $parser The instance that requests parsing.
@ -124,5 +126,4 @@ class AlterStatement extends NotImplementedStatement
//
parent::after($parser, $list, $token);
}
}

View File

@ -100,6 +100,8 @@ class CreateStatement extends Statement
public $body;
/**
* Function called before the token is processed.
*
* Parsing the `CREATE` statement.
*
* @param Parser $parser The instance that requests parsing.

View File

@ -37,6 +37,8 @@ class MaintenanceStatement extends Statement
public $tables;
/**
* Function called after the token was processed.
*
* Parses the additional options fragment at the end.
*
* @param Parser $parser The instance that requests parsing.

View File

@ -28,6 +28,8 @@ class NotImplementedStatement extends Statement
{
/**
* Function called after the token was processed.
*
* Jump to the end of the delimiter.
*
* @param Parser $parser The instance that requests parsing.

View File

@ -36,6 +36,8 @@ class RenameStatement extends Statement
public $renames;
/**
* Function called before the token is processed.
*
* Skips the `TABLE` keyword after `RENAME`.
*
* @param Parser $parser The instance that requests parsing.

View File

@ -68,17 +68,29 @@ class SelectStatement extends Statement
);
/**
* The sections of this statement, in order.
* The clauses of this statement, in order.
*
* Used by the query builder to arrange the clauses.
* @see Statement::$CLAUSES
*
* @var array
*/
public static $SECTIONS = array(
'SELECT' => 0, '%OPTIONS' => 1,'FROM' => 2, 'PARTITION' => 3,
'WHERE' => 4, 'GROUP BY' => 5, 'HAVING' => 6, 'ORDER BY' => 7,
'LIMIT' => 8, 'PROCEDURE' => 9, 'INTO' => 10, 'UNION' => 11,
'JOIN' => 12, '%OPTIONS' => 13
public static $CLAUSES = array(
'SELECT' => array('SELECT', 2),
// Used for options.
'_OPTIONS' => array('_OPTIONS', 1),
// Used for select expressions.
'_SELECT' => array('SELECT', 1),
'FROM' => array('FROM', 3),
'PARTITION' => array('PARTITION', 3),
'WHERE' => array('WHERE', 3),
'GROUP BY' => array('GROUP BY', 3),
'HAVING' => array('HAVING', 3),
'ORDER BY' => array('ORDER BY', 3),
'LIMIT' => array('LIMIT', 3),
'PROCEDURE' => array('PROCEDURE', 3),
'INTO' => array('INTO', 3),
'UNION' => array('UNION', 3),
'JOIN' => array('JOIN', 3),
);
/**
@ -86,33 +98,33 @@ class SelectStatement extends Statement
*
* @var FieldFragment[]
*/
public $expr;
public $expr = array();
/**
* Tables used as sources for this statement.
*
* @var FieldFragment[]
*/
public $from;
public $from = array();
/**
* Partitions used as source for this statement.
*
* @var ArrayFragment[]
* @var ArrayFragment
*/
public $partition;
/**
* Conditions used for filtering each row of the result set.
*
* @var WhereKeyword
* @var WhereKeyword[]
*/
public $where;
/**
* Conditions used for grouping the result set.
*
* @var GroupKeyword
* @var OrderKeyword[]
*/
public $group;
@ -157,4 +169,11 @@ class SelectStatement extends Statement
* @var JoinKeyword
*/
public $join;
/**
* Unions.
*
* @var SelectStatement[]
*/
public $union = array();
}

View File

@ -217,52 +217,52 @@ class Token
public function extract()
{
switch ($this->type) {
case Token::TYPE_KEYWORD:
if (!($this->flags & Token::FLAG_KEYWORD_RESERVED)) {
// Unreserved keywords should stay the way they are because they
// might represent field names.
return $this->token;
}
return strtoupper($this->token);
case Token::TYPE_WHITESPACE:
return ' ';
case Token::TYPE_BOOL:
return strtoupper($this->token) === 'TRUE';
case Token::TYPE_NUMBER:
$ret = str_replace('--', '', $this->token); // e.g. ---42 === -42
if ($this->flags & Token::FLAG_NUMBER_HEX) {
if ($this->flags & Token::FLAG_NUMBER_NEGATIVE) {
$ret = str_replace('-', '', $this->token);
sscanf($ret, "%x", $ret);
$ret = -$ret;
} else {
sscanf($ret, "%x", $ret);
case Token::TYPE_KEYWORD:
if (!($this->flags & Token::FLAG_KEYWORD_RESERVED)) {
// Unreserved keywords should stay the way they are because they
// might represent field names.
return $this->token;
}
} elseif (($this->flags & Token::FLAG_NUMBER_APPROXIMATE)
return strtoupper($this->token);
case Token::TYPE_WHITESPACE:
return ' ';
case Token::TYPE_BOOL:
return strtoupper($this->token) === 'TRUE';
case Token::TYPE_NUMBER:
$ret = str_replace('--', '', $this->token); // e.g. ---42 === -42
if ($this->flags & Token::FLAG_NUMBER_HEX) {
if ($this->flags & Token::FLAG_NUMBER_NEGATIVE) {
$ret = str_replace('-', '', $this->token);
sscanf($ret, "%x", $ret);
$ret = -$ret;
} else {
sscanf($ret, "%x", $ret);
}
} elseif (($this->flags & Token::FLAG_NUMBER_APPROXIMATE)
|| ($this->flags & Token::FLAG_NUMBER_FLOAT)
) {
sscanf($ret, "%f", $ret);
} else {
sscanf($ret, "%d", $ret);
}
return $ret;
case Token::TYPE_STRING:
$quote = $this->token[0];
$str = str_replace($quote . $quote, $quote, $this->token);
return mb_substr($str, 1, -1); // trims quotes
case Token::TYPE_SYMBOL:
$str = $this->token;
if ((isset($str[0])) && ($str[0] === '@')) {
$str = mb_substr($str, 1);
}
if ((isset($str[0])) && (($str[0] === '`')
) {
sscanf($ret, "%f", $ret);
} else {
sscanf($ret, "%d", $ret);
}
return $ret;
case Token::TYPE_STRING:
$quote = $this->token[0];
$str = str_replace($quote . $quote, $quote, $this->token);
return mb_substr($str, 1, -1); // trims quotes
case Token::TYPE_SYMBOL:
$str = $this->token;
if ((isset($str[0])) && ($str[0] === '@')) {
$str = mb_substr($str, 1);
}
if ((isset($str[0])) && (($str[0] === '`')
|| ($str[0] === '"') || ($str[0] === '\''))
) {
$quote = $str[0];
$str = str_replace($quote . $quote, $quote, $str);
$str = mb_substr($str, 1, -1);
}
return $str;
) {
$quote = $str[0];
$str = str_replace($quote . $quote, $quote, $str);
$str = mb_substr($str, 1, -1);
}
return $str;
}
return $this->token;
}

View File

@ -222,32 +222,32 @@ class Query
if ($statement instanceof AlterStatement) {
$flags['querytype'] = 'ALTER';
$flags['reload'] = true;
} else if ($statement instanceof CreateStatement) {
} elseif ($statement instanceof CreateStatement) {
$flags['querytype'] = 'CREATE';
$flags['reload'] = true;
} else if ($statement instanceof AnalyzeStatement) {
} elseif ($statement instanceof AnalyzeStatement) {
$flags['querytype'] = 'ANALYZE';
$flags['is_maint'] = true;
} else if ($statement instanceof CheckStatement) {
} elseif ($statement instanceof CheckStatement) {
$flags['querytype'] = 'CHECK';
$flags['is_maint'] = true;
} else if ($statement instanceof ChecksumStatement) {
} elseif ($statement instanceof ChecksumStatement) {
$flags['querytype'] = 'CHECKSUM';
$flags['is_maint'] = true;
} else if ($statement instanceof OptimizeStatement) {
} elseif ($statement instanceof OptimizeStatement) {
$flags['querytype'] = 'OPTIMIZE';
$flags['is_maint'] = true;
} else if ($statement instanceof RepairStatement) {
} elseif ($statement instanceof RepairStatement) {
$flags['querytype'] = 'REPAIR';
$flags['is_maint'] = true;
} else if ($statement instanceof CallStatement) {
} elseif ($statement instanceof CallStatement) {
$flags['querytype'] = 'CALL';
$flags['is_procedure'] = true;
} else if ($statement instanceof DeleteStatement) {
} elseif ($statement instanceof DeleteStatement) {
$flags['querytype'] = 'DELETE';
$flags['is_delete'] = true;
$flags['is_affected'] = true;
} else if ($statement instanceof DropStatement) {
} elseif ($statement instanceof DropStatement) {
$flags['querytype'] = 'DROP';
$flags['reload'] = true;
@ -256,19 +256,19 @@ class Query
) {
$flags['drop_database'] = true;
}
} else if ($statement instanceof ExplainStatement) {
} elseif ($statement instanceof ExplainStatement) {
$flags['querytype'] = 'EXPLAIN';
$flags['is_explain'] = true;
} else if ($statement instanceof InsertStatement) {
} elseif ($statement instanceof InsertStatement) {
$flags['querytype'] = 'INSERT';
$flags['is_affected'] = true;
$flags['is_insert'] = true;
} else if ($statement instanceof ReplaceStatement) {
} elseif ($statement instanceof ReplaceStatement) {
$flags['querytype'] = 'REPLACE';
$flags['is_affected'] = true;
$flags['is_replace'] = true;
$flags['is_insert'] = true;
} else if ($statement instanceof SelectStatement) {
} elseif ($statement instanceof SelectStatement) {
$flags['querytype'] = 'SELECT';
$flags['is_select'] = true;
@ -294,7 +294,7 @@ class Query
if (!empty($expr->function)) {
if ($expr->function === 'COUNT') {
$flags['is_count'] = true;
} else if (in_array($expr->function, static::$FUNCTIONS)) {
} elseif (in_array($expr->function, static::$FUNCTIONS)) {
$flags['is_func'] = true;
}
}
@ -325,10 +325,10 @@ class Query
$flags['join'] = true;
}
} else if ($statement instanceof ShowStatement) {
} elseif ($statement instanceof ShowStatement) {
$flags['querytype'] = 'SHOW';
$flags['is_show'] = true;
} else if ($statement instanceof UpdateStatement) {
} elseif ($statement instanceof UpdateStatement) {
$flags['querytype'] = 'UPDATE';
$flags['is_affected'] = true;
}
@ -409,7 +409,7 @@ class Query
/**
* Gets the type of clause.
*
* @param string $clause The clause.
* @param string $clause The clause.
*
* @return string
*/
@ -450,7 +450,7 @@ class Query
{
/**
* Current location.
* The index of the current clause.
* @var int
*/
$currIdx = 0;
@ -469,19 +469,20 @@ class Query
$ret = '';
/**
* The place where the clause should be added.
* The clauses of this type of statement and their index.
* @var array
*/
$clauses = array_flip(array_keys($statement::$CLAUSES));
/**
* The index of this clause.
* @var int
*/
$clauseIdx = $statement::$SECTIONS[static::getClauseType($clause)];
$clauseIdx = $clauses[static::getClauseType($clause)];
for ($i = $statement->first; $i <= $statement->last; ++$i) {
$token = $list->tokens[$i];
if ($token->type === Token::TYPE_DELIMITER) {
break;
}
if ($token->type === Token::TYPE_OPERATOR) {
if ($token->value === '(') {
++$brackets;
@ -493,9 +494,9 @@ class Query
if ($brackets == 0) {
// Checking if we changed sections.
if ($token->type === Token::TYPE_KEYWORD) {
if (isset($statement::$SECTIONS[$token->value])) {
if ($statement::$SECTIONS[$token->value] >= $currIdx) {
$currIdx = $statement::$SECTIONS[$token->value];
if (isset($clauses[$token->value])) {
if ($clauses[$token->value] >= $currIdx) {
$currIdx = $clauses[$token->value];
if (($skipFirst) && ($currIdx == $clauseIdx)) {
// This token is skipped (not added to the old
// clause) because it will be replaced.
@ -546,5 +547,4 @@ class Query
$clause . ' ' .
static::getClause($statement, $list, $clause, 1, false);
}
}

View File

@ -43,7 +43,6 @@ class Table
$ret = array();
foreach ($statement->fields as $field) {
if ((empty($field->key)) || ($field->key->type !== 'FOREIGN KEY')) {
continue;
}
@ -93,7 +92,6 @@ class Table
$ret = array();
foreach ($statement->fields as $field) {
// Skipping keys.
if (empty($field->type)) {
continue;
@ -105,7 +103,6 @@ class Table
);
if ($field->options) {
if ($field->type->name === 'TIMESTAMP') {
if ($field->options->has('NOT NULL')) {
$ret[$field->name]['timestamp_not_null'] = true;