Merge pull request #11319 from udan11/update

Updated sql-parser library.
This commit is contained in:
Marc Delisle 2015-07-25 12:21:35 -04:00
commit 2a63cbc43f
17 changed files with 464 additions and 119 deletions

View File

@ -10,64 +10,84 @@
*
* @package SqlParser
*/
namespace SqlParser;
/**
* A component (of a statement) is a part of a statement that is common to
* multiple query types.
*
* @category Components
* @package SqlParser
* @author Dan Ungureanu <udan1107@gmail.com>
* @license http://opensource.org/licenses/GPL-2.0 GNU Public License
*/
abstract class Component
{
namespace {
/**
* Parses the tokens contained in the given list in the context of the given
* parser.
*
* @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 mixed
*/
public static function parse(
Parser $parser, TokensList $list, array $options = array()
) {
// This method should be abstract, but it can't be both static and
// abstract.
return null;
}
if (!function_exists('__')) {
/**
* Builds the string representation of a component of this type.
*
* In other words, this function represents the inverse function of
* `static::parse`.
*
* @param mixed $component The component to be built.
*
* @return string
*/
public static function build($component)
{
// This method should be abstract, but it can't be both static and
// abstract.
return null;
}
/**
* Builds the string representation of a component of this type.
*
* @see static::build
*
* @return string
*/
public function __toString()
{
return static::build($this);
/**
* Translates the given string.
*
* @param string $str String to be translated.
*
* @return string
*/
function __($str)
{
return $str;
}
}
}
namespace SqlParser {
/**
* A component (of a statement) is a part of a statement that is common to
* multiple query types.
*
* @category Components
* @package SqlParser
* @author Dan Ungureanu <udan1107@gmail.com>
* @license http://opensource.org/licenses/GPL-2.0 GNU Public License
*/
abstract class Component
{
/**
* Parses the tokens contained in the given list in the context of the given
* parser.
*
* @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 mixed
*/
public static function parse(
Parser $parser, TokensList $list, array $options = array()
) {
// This method should be abstract, but it can't be both static and
// abstract.
throw new \Exception(\__('Not implemented yet.'));
}
/**
* Builds the string representation of a component of this type.
*
* In other words, this function represents the inverse function of
* `static::parse`.
*
* @param mixed $component The component to be built.
*
* @return string
*/
public static function build($component)
{
// This method should be abstract, but it can't be both static and
// abstract.
throw new \Exception(\__('Not implemented yet.'));
}
/**
* Builds the string representation of a component of this type.
*
* @see static::build
*
* @return string
*/
public function __toString()
{
return static::build($this);
}
}
}

View File

@ -175,7 +175,10 @@ class Expression extends Component
continue;
}
if (($token->type === Token::TYPE_KEYWORD) && ($token->flags & Token::FLAG_KEYWORD_RESERVED)) {
if (($token->type === Token::TYPE_KEYWORD)
&& ($token->flags & Token::FLAG_KEYWORD_RESERVED)
&& ($token->value !== 'DUAL')
) {
// Keywords may be found only between brackets.
if ($brackets === 0) {
if ((empty($options['noAlias'])) && ($token->value === 'AS')) {

View File

@ -67,7 +67,10 @@ class ExpressionArray extends Component
continue;
}
if (($token->type === Token::TYPE_KEYWORD) && ($token->flags & Token::FLAG_KEYWORD_RESERVED)) {
if (($token->type === Token::TYPE_KEYWORD)
&& ($token->flags & Token::FLAG_KEYWORD_RESERVED)
&& ($token->value !== 'DUAL')
) {
// No keyword is expected.
break;
}

View File

@ -241,6 +241,13 @@ class FieldDefinition extends Component
$state = 6;
++$list->idx;
break;
} else {
$parser->error(
__('A comma or a closing bracket was expected.'),
$token
);
$state = 0;
break;
}
}
}

View File

@ -37,7 +37,19 @@ class OrderKeyword extends Component
*
* @var string
*/
public $type = 'ASC';
public $type;
/**
* Constructor.
*
* @param Expression $field The field that we are sorting by.
* @param string $type The sorting type.
*/
public function __construct($field = null, $type = 'ASC')
{
$this->field = $field;
$this->type = $type;
}
/**
* @param Parser $parser The parser that serves as context.
@ -110,4 +122,22 @@ class OrderKeyword extends Component
--$list->idx;
return $ret;
}
/**
* @param OrderKeyword $component The component to be built.
*
* @return string
*/
public static function build($component)
{
if (is_array($component)) {
$ret = array();
foreach ($component as $c) {
$ret[] = static::build($c);
}
return implode(", ", $ret);
} else {
return Expression::build($component->field) . ' ' . $component->type;
}
}
}

View File

@ -117,4 +117,22 @@ class SetOperation extends Component
--$list->idx;
return $ret;
}
/**
* @param SetOperation|SetOperation[] $component The component to be built.
*
* @return string
*/
public static function build($component)
{
if (is_array($component)) {
$ret = array();
foreach ($component as $c) {
$ret[] = static::build($c);
}
return implode(", ", $ret);
} else {
return $component->column . ' = ' . $component->value;
}
}
}

View File

@ -0,0 +1,41 @@
<?php
/**
* `UNION` keyword builder.
*
* @package SqlParser
* @subpackage Components
*/
namespace SqlParser\Components;
use SqlParser\Component;
use SqlParser\Parser;
use SqlParser\Token;
use SqlParser\TokensList;
/**
* `UNION` keyword builder.
*
* @category Keywords
* @package SqlParser
* @subpackage Components
* @author Dan Ungureanu <udan1107@gmail.com>
* @license http://opensource.org/licenses/GPL-2.0 GNU Public License
*/
class UnionKeyword extends Component
{
/**
* @param SelectStatement[] $component The component to be built.
*
* @return string
*/
public static function build($component)
{
$ret = array();
foreach ($component as $c) {
$ret[] = $c->build();
}
return implode(" UNION ", $ret);
}
}

View File

@ -144,15 +144,18 @@ class ContextMySql50000 extends Context
'SQL_CALC_FOUND_ROWS' => 3,
'GROUP BY' => 7, 'NOT NULL' => 7, 'ORDER BY' => 7, 'SET NULL' => 7,
'FULL JOIN' => 7, 'IF EXISTS' => 7, 'LEFT JOIN' => 7, 'NO ACTION' => 7,
'ON DELETE' => 7, 'ON UPDATE' => 7,
'INNER JOIN' => 7, 'OR REPLACE' => 7, 'RIGHT JOIN' => 7,
'FOR EACH ROW' => 7, 'SQL SECURITY' => 7,
'AND CHAIN' => 7, 'FULL JOIN' => 7, 'IF EXISTS' => 7, 'LEFT JOIN' => 7,
'NO ACTION' => 7, 'ON DELETE' => 7, 'ON UPDATE' => 7,
'INNER JOIN' => 7, 'NO RELEASE' => 7, 'OR REPLACE' => 7, 'RIGHT JOIN' => 7,
'AND NO CHAIN' => 7, 'FOR EACH ROW' => 7, 'SQL SECURITY' => 7,
'CHARACTER SET' => 7, 'IF NOT EXISTS' => 7,
'DATA DIRECTORY' => 7,
'DEFAULT CHARSET' => 7, 'DEFAULT COLLATE' => 7, 'INDEX DIRECTORY' => 7,
'GENERATED ALWAYS' => 7,
'START TRANSACTION' => 7,
'SELECT TRANSACTION' => 7,
'DEFAULT CHARACTER SET' => 7,
'WITH CONSISTENT SNAPSHOT' => 7,
'XML' => 9,
'ENUM' => 9, 'TEXT' => 9,

View File

@ -156,15 +156,18 @@ class ContextMySql50100 extends Context
'MASTER_SSL_VERIFY_SERVER_CERT' => 3,
'GROUP BY' => 7, 'NOT NULL' => 7, 'ORDER BY' => 7, 'SET NULL' => 7,
'FULL JOIN' => 7, 'IF EXISTS' => 7, 'LEFT JOIN' => 7, 'NO ACTION' => 7,
'ON DELETE' => 7, 'ON UPDATE' => 7,
'INNER JOIN' => 7, 'OR REPLACE' => 7, 'RIGHT JOIN' => 7,
'FOR EACH ROW' => 7, 'SQL SECURITY' => 7,
'AND CHAIN' => 7, 'FULL JOIN' => 7, 'IF EXISTS' => 7, 'LEFT JOIN' => 7,
'NO ACTION' => 7, 'ON DELETE' => 7, 'ON UPDATE' => 7,
'INNER JOIN' => 7, 'NO RELEASE' => 7, 'OR REPLACE' => 7, 'RIGHT JOIN' => 7,
'AND NO CHAIN' => 7, 'FOR EACH ROW' => 7, 'SQL SECURITY' => 7,
'CHARACTER SET' => 7, 'IF NOT EXISTS' => 7,
'DATA DIRECTORY' => 7,
'DEFAULT CHARSET' => 7, 'DEFAULT COLLATE' => 7, 'INDEX DIRECTORY' => 7,
'GENERATED ALWAYS' => 7,
'START TRANSACTION' => 7,
'SELECT TRANSACTION' => 7,
'DEFAULT CHARACTER SET' => 7,
'WITH CONSISTENT SNAPSHOT' => 7,
'XML' => 9,
'ENUM' => 9, 'TEXT' => 9,

View File

@ -161,15 +161,18 @@ class ContextMySql50500 extends Context
'MASTER_SSL_VERIFY_SERVER_CERT' => 3,
'GROUP BY' => 7, 'NOT NULL' => 7, 'ORDER BY' => 7, 'SET NULL' => 7,
'FULL JOIN' => 7, 'IF EXISTS' => 7, 'LEFT JOIN' => 7, 'NO ACTION' => 7,
'ON DELETE' => 7, 'ON UPDATE' => 7,
'INNER JOIN' => 7, 'OR REPLACE' => 7, 'RIGHT JOIN' => 7,
'FOR EACH ROW' => 7, 'SQL SECURITY' => 7,
'AND CHAIN' => 7, 'FULL JOIN' => 7, 'IF EXISTS' => 7, 'LEFT JOIN' => 7,
'NO ACTION' => 7, 'ON DELETE' => 7, 'ON UPDATE' => 7,
'INNER JOIN' => 7, 'NO RELEASE' => 7, 'OR REPLACE' => 7, 'RIGHT JOIN' => 7,
'AND NO CHAIN' => 7, 'FOR EACH ROW' => 7, 'SQL SECURITY' => 7,
'CHARACTER SET' => 7, 'IF NOT EXISTS' => 7,
'DATA DIRECTORY' => 7,
'DEFAULT CHARSET' => 7, 'DEFAULT COLLATE' => 7, 'INDEX DIRECTORY' => 7,
'GENERATED ALWAYS' => 7,
'START TRANSACTION' => 7,
'SELECT TRANSACTION' => 7,
'DEFAULT CHARACTER SET' => 7,
'WITH CONSISTENT SNAPSHOT' => 7,
'XML' => 9,
'ENUM' => 9, 'TEXT' => 9,

View File

@ -166,15 +166,18 @@ class ContextMySql50600 extends Context
'MASTER_SSL_VERIFY_SERVER_CERT' => 3,
'GROUP BY' => 7, 'NOT NULL' => 7, 'ORDER BY' => 7, 'SET NULL' => 7,
'FULL JOIN' => 7, 'IF EXISTS' => 7, 'LEFT JOIN' => 7, 'NO ACTION' => 7,
'ON DELETE' => 7, 'ON UPDATE' => 7,
'INNER JOIN' => 7, 'OR REPLACE' => 7, 'RIGHT JOIN' => 7,
'FOR EACH ROW' => 7, 'SQL SECURITY' => 7,
'AND CHAIN' => 7, 'FULL JOIN' => 7, 'IF EXISTS' => 7, 'LEFT JOIN' => 7,
'NO ACTION' => 7, 'ON DELETE' => 7, 'ON UPDATE' => 7,
'INNER JOIN' => 7, 'NO RELEASE' => 7, 'OR REPLACE' => 7, 'RIGHT JOIN' => 7,
'AND NO CHAIN' => 7, 'FOR EACH ROW' => 7, 'SQL SECURITY' => 7,
'CHARACTER SET' => 7, 'IF NOT EXISTS' => 7,
'DATA DIRECTORY' => 7,
'DEFAULT CHARSET' => 7, 'DEFAULT COLLATE' => 7, 'INDEX DIRECTORY' => 7,
'GENERATED ALWAYS' => 7,
'START TRANSACTION' => 7,
'SELECT TRANSACTION' => 7,
'DEFAULT CHARACTER SET' => 7,
'WITH CONSISTENT SNAPSHOT' => 7,
'XML' => 9,
'ENUM' => 9, 'TEXT' => 9,

View File

@ -174,15 +174,18 @@ class ContextMySql50700 extends Context
'MASTER_SSL_VERIFY_SERVER_CERT' => 3,
'GROUP BY' => 7, 'NOT NULL' => 7, 'ORDER BY' => 7, 'SET NULL' => 7,
'FULL JOIN' => 7, 'IF EXISTS' => 7, 'LEFT JOIN' => 7, 'NO ACTION' => 7,
'ON DELETE' => 7, 'ON UPDATE' => 7,
'INNER JOIN' => 7, 'OR REPLACE' => 7, 'RIGHT JOIN' => 7,
'FOR EACH ROW' => 7, 'SQL SECURITY' => 7,
'AND CHAIN' => 7, 'FULL JOIN' => 7, 'IF EXISTS' => 7, 'LEFT JOIN' => 7,
'NO ACTION' => 7, 'ON DELETE' => 7, 'ON UPDATE' => 7,
'INNER JOIN' => 7, 'NO RELEASE' => 7, 'OR REPLACE' => 7, 'RIGHT JOIN' => 7,
'AND NO CHAIN' => 7, 'FOR EACH ROW' => 7, 'SQL SECURITY' => 7,
'CHARACTER SET' => 7, 'IF NOT EXISTS' => 7,
'DATA DIRECTORY' => 7,
'DEFAULT CHARSET' => 7, 'DEFAULT COLLATE' => 7, 'INDEX DIRECTORY' => 7,
'GENERATED ALWAYS' => 7,
'START TRANSACTION' => 7,
'SELECT TRANSACTION' => 7,
'DEFAULT CHARACTER SET' => 7,
'WITH CONSISTENT SNAPSHOT' => 7,
'XML' => 9,
'ENUM' => 9, 'TEXT' => 9,

View File

@ -28,8 +28,9 @@ namespace {
namespace SqlParser {
use SqlParser\Statements\SelectStatement;
use SqlParser\Exceptions\ParserException;
use SqlParser\Statements\SelectStatement;
use SqlParser\Statements\TransactionStatement;
/**
* Takes multiple tokens (contained in a Lexer instance) as input and builds a
@ -50,47 +51,54 @@ namespace SqlParser {
*/
public static $STATEMENT_PARSERS = array(
'EXPLAIN' => 'SqlParser\\Statements\\ExplainStatement',
'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',
'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',
'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',
'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',
'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' => '',
'PREPARE' => '',
'EXECUTE' => '',
// Transactional and Locking Statements
// https://dev.mysql.com/doc/refman/5.7/en/commit.html
'START TRANSACTION' => 'SqlParser\\Statements\\TransactionStatement',
'BEGIN' => 'SqlParser\\Statements\\TransactionStatement',
'COMMIT' => 'SqlParser\\Statements\\TransactionStatement',
'ROLLBACK' => 'SqlParser\\Statements\\TransactionStatement',
);
/**
@ -106,6 +114,12 @@ namespace SqlParser {
'field' => 'options',
),
// This is used only for building.
'UNION' => array(
'class' => 'SqlParser\\Components\\UnionKeyword',
'field' => 'union',
),
'ALTER' => array(
'class' => 'SqlParser\\Components\\Expression',
'field' => 'table',
@ -311,6 +325,12 @@ namespace SqlParser {
public function parse()
{
/**
* Last transaction.
* @var TransactionStatement
*/
$lastTransaction = null;
/**
* Last parsed statement.
* @var Statement $lastStatement
@ -321,7 +341,7 @@ namespace SqlParser {
* Whether a union is parsed or not.
* @var bool $inUnion
*/
$inUnion = true;
$inUnion = false;
/**
* The index of the last token from the last statement.
@ -400,21 +420,62 @@ namespace SqlParser {
$statement->last = $list->idx;
$prevLastIdx = $list->idx;
// Finally, storing the statement.
// Handles unions.
if (($inUnion)
&& ($lastStatement instanceof SelectStatement)
&& ($statement instanceof SelectStatement)
) {
/**
* Last SELECT statement.
* @var SelectStatement $lastStatement
*/
$lastStatement->union[] = $statement;
// if there are no no delimiting brackets, the `ORDER` and
// `LIMIT` keywords actually belong to the first statement.
$lastStatement->order = $statement->order;
$lastStatement->limit = $statement->limit;
$statement->order = array();
$statement->limit = null;
// The statement actually ends where the last statement in
// union ends.
$lastStatement->last = $statement->last;
$inUnion = false;
continue;
}
// Handles transactions.
if ($statement instanceof TransactionStatement) {
if ($statement->type === TransactionStatement::TYPE_BEGIN) {
$lastTransaction = $statement;
$this->statements[] = $statement;
} elseif ($statement->type === TransactionStatement::TYPE_END) {
if ($lastTransaction === null) {
// Even though an error occurred, the query is being
// saved.
$this->statements[] = $statement;
$this->error(
__('No transaction was previously started.'),
$token
);
} else {
$lastTransaction->end = $statement;
}
$lastTransaction = null;
}
continue;
}
// Finally, storing the statement.
if ($lastTransaction !== null) {
$lastTransaction->statements[] = $statement;
} else {
$this->statements[] = $statement;
$lastStatement = $statement;
}
$lastStatement = $statement;
}
}

View File

@ -122,11 +122,6 @@ abstract class Statement
*/
$type = $clause[1];
// Checking if there is any parser (builder) for this clause.
if (empty(Parser::$KEYWORD_PARSERS[$name])) {
continue;
}
/**
* The builder (parser) of this clause.
* @var string $class
@ -169,6 +164,16 @@ abstract class Statement
*/
public function parse(Parser $parser, TokensList $list)
{
/**
* Whether the beginning of this statement was previously parsed.
*
* This is used to delimit statements that don't use the usual
* delimiters.
*
* @var bool
*/
$parsedBeginning = false;
// This may be corrected by the parser.
$this->first = $list->idx;
@ -178,7 +183,7 @@ abstract class Statement
* default.
* @var bool $parsedOptions
*/
$parsedOptions = !empty(static::$OPTIONS) ? false : true;
$parsedOptions = empty(static::$OPTIONS);
for (; $list->idx < $list->count; ++$list->idx) {
/**
@ -236,8 +241,21 @@ abstract class Statement
}
if (!empty(Parser::$STATEMENT_PARSERS[$token->value])) {
if ($parsedBeginning) {
// New statement has started. We let the parser construct a
// new statement and parse that one
$parser->error(
__('A new statement was found, but no delimiter between them.'),
$token
);
break;
}
$parsedBeginning = true;
if (!$parsedOptions) {
++$list->idx; // Skipping keyword.
if (empty(static::$OPTIONS[$token->value])) {
// Skipping keyword because if it is not a option.
++$list->idx;
}
$this->options = OptionsArray::parse(
$parser,
$list,
@ -256,7 +274,7 @@ abstract class Statement
// Parsing this keyword.
if ($class !== null) {
++$list->idx; // Skipping keyword.
++$list->idx; // Skipping keyword or last option.
$this->$field = $class::parse($parser, $list, $options);
}

View File

@ -99,6 +99,9 @@ class SelectStatement extends Statement
'PROCEDURE' => array('PROCEDURE', 3),
'INTO' => array('INTO', 3),
'UNION' => array('UNION', 3),
// These are available only when `UNION` is present.
// 'ORDER BY' => array('ORDER BY', 3),
// 'LIMIT' => array('LIMIT', 3),
);
/**

View File

@ -0,0 +1,119 @@
<?php
/**
* Transaction statement.
*
* @package SqlParser
* @subpackage Statements
*/
namespace SqlParser\Statements;
use SqlParser\Parser;
use SqlParser\Statement;
use SqlParser\Token;
use SqlParser\TokensList;
use SqlParser\Components\Expression;
use SqlParser\Components\OptionsArray;
/**
* Transaction statement.
*
* @category Statements
* @package SqlParser
* @subpackage Statements
* @author Dan Ungureanu <udan1107@gmail.com>
* @license http://opensource.org/licenses/GPL-2.0 GNU Public License
*/
class TransactionStatement extends Statement
{
/**
* START TRANSACTION and BEGIN
*
* @var int
*/
const TYPE_BEGIN = 1;
/**
* COMMIT and ROLLBACK
*
* @var int
*/
const TYPE_END = 2;
/**
* The type of this query.
*
* @var int
*/
public $type;
/**
* The list of statements in this transaction.
*
* @var Statements[]
*/
public $statements;
/**
* The ending transaction statement which may be a `COMMIT` or a `ROLLBACK`.
*
* @var TransactionStatement
*/
public $end;
/**
* Options for this query.
*
* @var array
*/
public static $OPTIONS = array(
'START TRANSACTION' => 1,
'BEGIN' => 1,
'COMMIT' => 1,
'ROLLBACK' => 1,
'WITH CONSISTENT SNAPSHOT' => 2,
'WORK' => 2,
'AND NO CHAIN' => 3,
'AND CHAIN' => 3,
'RELEASE' => 4,
'NO RELEASE' => 4,
);
/**
* @param Parser $parser The instance that requests parsing.
* @param TokensList $list The list of tokens to be parsed.
*
* @return void
*/
public function parse(Parser $parser, TokensList $list)
{
parent::parse($parser, $list);
// Checks the type of this query.
if (($this->options->has('START TRANSACTION'))
|| ($this->options->has('BEGIN'))
) {
$this->type = TransactionStatement::TYPE_BEGIN;
} elseif (($this->options->has('COMMIT'))
|| ($this->options->has('ROLLBACK'))
) {
$this->type = TransactionStatement::TYPE_END;
}
}
/**
* @return string
*/
public function build()
{
$ret = OptionsArray::build($this->options);
if ($this->type === TransactionStatement::TYPE_BEGIN) {
foreach ($this->statements as $statement) {
$ret .= ';' . $statement->build();
}
$ret .= ';' . $this->end->build();
}
return $ret;
}
}

View File

@ -533,6 +533,14 @@ class Query
*/
$clauses = array_flip(array_keys($statement::$CLAUSES));
// This is a cheap fix for `SELECT` statements that contain `UNION`.
// Replacing the `ORDER BY` or `LIMIT` clauses should replace the last
// clause.
if (($statement instanceof SelectStatement) && (!empty($statement->union))) {
$clauses['ORDER BY'] = count($clauses) + 1;
$clauses['LIMIT'] = count($clauses) + 2;
}
/**
* Lexer used for lexing the clause.
* @var Lexer $lexer
@ -552,10 +560,9 @@ class Query
$clauseIdx = $clauses[$clauseType];
$firstClauseIdx = $clauseIdx;
$lastClauseIdx = $clauseIdx + 1;
// Determining the behaviour of this function.
// Determining the behavior of this function.
if ($type === -1) {
$firstClauseIdx = -1; // Something small enough.
$lastClauseIdx = $clauseIdx - 1;