Merge pull request #11401 from udan11/update

Updated sql-parser.
This commit is contained in:
Marc Delisle 2015-08-16 05:36:39 -04:00
commit 09837fade0
36 changed files with 796 additions and 201 deletions

View File

@ -108,6 +108,7 @@ class AlterOperation extends Component
/**
* Counts brackets.
*
* @var int $brackets
*/
$brackets = 0;
@ -123,13 +124,15 @@ class AlterOperation extends Component
*
* 2 -------------------------[ , ]-----------------------> 0
*
* @var int
* @var int $state
*/
$state = 0;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
*
* @var Token $token
*/
$token = $list->tokens[$list->idx];

View File

@ -38,7 +38,8 @@ class Array2d extends Component
/**
* The number of values in each set.
* @var int
*
* @var int $count
*/
$count = -1;
@ -47,18 +48,20 @@ class Array2d extends Component
*
* Below are the states of the parser.
*
* 0 ----------------------[ array ]---------------------> 1
* 0 ----------------------[ array ]----------------------> 1
*
* 1 ------------------------[ , ]------------------------> 0
* 1 -----------------------[ else ]----------------------> -1
* 1 -----------------------[ else ]----------------------> (END)
*
* @var int
* @var int $state
*/
$state = 0;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
*
* @var Token $token
*/
$token = $list->tokens[$list->idx];
@ -118,4 +121,14 @@ class Array2d extends Component
--$list->idx;
return $ret;
}
/**
* @param ArrayObj[] $component The component to be built.
*
* @return string
*/
public static function build($component)
{
return ArrayObj::build($component);
}
}

View File

@ -56,11 +56,11 @@ class ArrayObj extends Component
* @param TokensList $list The list of tokens that are being parsed.
* @param array $options Parameters for parsing.
*
* @return ArrayObj
* @return mixed
*/
public static function parse(Parser $parser, TokensList $list, array $options = array())
{
$ret = new ArrayObj();
$ret = empty($options['type']) ? new ArrayObj() : array();
/**
* The state of the parser.
@ -72,15 +72,17 @@ class ArrayObj extends Component
* 1 ------------------[ array element ]-----------------> 2
*
* 2 ------------------------[ , ]-----------------------> 1
* 2 ------------------------[ ) ]-----------------------> -1
* 2 ------------------------[ ) ]-----------------------> (END)
*
* @var int
* @var int $state
*/
$state = 0;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
*
* @var Token $token
*/
$token = $list->tokens[$list->idx];
@ -109,8 +111,16 @@ class ArrayObj extends Component
// Empty array.
break;
}
$ret->values[] = $token->value;
$ret->raw[] = $token->token;
if (empty($options['type'])) {
$ret->values[] = $token->value;
$ret->raw[] = $token->token;
} else {
$ret[] = $options['type']::parse(
$parser,
$list,
empty($options['typeOptions']) ? array() : $options['typeOptions']
);
}
$state = 2;
} elseif ($state === 2) {
if (($token->type !== Token::TYPE_OPERATOR) || (($token->value !== ',') && ($token->value !== ')'))) {
@ -133,20 +143,28 @@ class ArrayObj extends Component
}
/**
* @param ArrayObj $component The component to be built.
* @param ArrayObj|ArrayObj[] $component The component to be built.
*
* @return string
*/
public static function build($component)
{
$values = array();
if (!empty($component->raw)) {
$values = $component->raw;
} else {
foreach ($component->values as $value) {
$values[] = $value;
if (is_array($component)) {
$values = array();
foreach ($component as $c) {
$values[] = static::build($c);
}
return implode(', ', $values);
} else {
$values = array();
if (!empty($component->raw)) {
$values = $component->raw;
} else {
foreach ($component->values as $value) {
$values[] = $value;
}
}
return '(' . implode(', ', $values) . ')';
}
return '(' . implode(', ', $values) . ')';
}
}

View File

@ -95,6 +95,7 @@ class Condition extends Component
/**
* Counts brackets.
*
* @var int $brackets
*/
$brackets = 0;
@ -104,7 +105,8 @@ class Condition extends Component
* It is required to keep track of them because their structure contains
* the keyword `AND`, which is also an operator that delimits
* expressions.
* @var bool
*
* @var bool $betweenBefore
*/
$betweenBefore = false;
@ -112,6 +114,7 @@ class Condition extends Component
/**
* Token parsed at this moment.
*
* @var Token $token
*/
$token = $list->tokens[$list->idx];

View File

@ -173,13 +173,15 @@ class CreateDefinition extends Component
* 5 ------------------------[ , ]-----------------------> 1
* 5 ------------------------[ ) ]-----------------------> 6 (-1)
*
* @var int
* @var int $state
*/
$state = 0;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
*
* @var Token $token
*/
$token = $list->tokens[$list->idx];

View File

@ -103,13 +103,15 @@ class DataType extends Component
*
* 1 ----------------[ size and options ]----------------> 2
*
* @var int
* @var int $state
*/
$state = 0;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
*
* @var Token $token
*/
$token = $list->tokens[$list->idx];

View File

@ -122,24 +122,28 @@ class Expression extends Component
/**
* Whether current tokens make an expression or a table reference.
*
* @var bool $isExpr
*/
$isExpr = false;
/**
* Whether a period was previously found.
*
* @var bool $dot
*/
$dot = false;
/**
* Whether an alias is expected. Is 2 if `AS` keyword was found.
*
* @var int $alias
*/
$alias = 0;
/**
* Counts brackets.
*
* @var int $brackets
*/
$brackets = 0;
@ -150,13 +154,16 @@ class Expression extends Component
* string, if function was previously found;
* true, if opening bracket was previously found;
* null, in any other case.
*
* @var string|bool $prev
*/
$prev = null;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
*
* @var Token $token
*/
$token = $list->tokens[$list->idx];

View File

@ -44,15 +44,17 @@ class ExpressionArray extends Component
* 0 ----------------------[ array ]---------------------> 1
*
* 1 ------------------------[ , ]------------------------> 0
* 1 -----------------------[ else ]----------------------> -1
* 1 -----------------------[ else ]----------------------> (END)
*
* @var int
* @var int $state
*/
$state = 0;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
*
* @var Token $token
*/
$token = $list->tokens[$list->idx];

View File

@ -73,15 +73,17 @@ class FunctionCall extends Component
*
* 0 ----------------------[ name ]-----------------------> 1
*
* 1 --------------------[ parameters ]-------------------> -1
* 1 --------------------[ parameters ]-------------------> (END)
*
* @var int
* @var int $state
*/
$state = 0;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
*
* @var Token $token
*/
$token = $list->tokens[$list->idx];

View File

@ -65,17 +65,19 @@ class IntoKeyword extends Component
* 0 -----------------------[ name ]----------------------> 1
* 0 ---------------------[ OUTFILE ]---------------------> 2
*
* 1 ------------------------[ ( ]------------------------> -1
* 1 ------------------------[ ( ]------------------------> (END)
*
* 2 ---------------------[ filename ]--------------------> 1
*
* @var int
* @var int $state
*/
$state = 0;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
*
* @var Token $token
*/
$token = $list->tokens[$list->idx];
@ -128,4 +130,20 @@ class IntoKeyword extends Component
--$list->idx;
return $ret;
}
/**
* @param IntoKeyword $component The component to be built.
*
* @return string
*/
public static function build($component)
{
if ($component->type === 'OUTFILE') {
return 'OUTFILE "' . $component->dest . '"';
} else {
$columns = !empty($component->columns) ?
'(' . implode(', ', $component->columns) . ')' : '';
return Expression::build($component->dest) . $columns;
}
}
}

View File

@ -86,7 +86,7 @@ class JoinKeyword extends Component
*
* 3 --------------------[ conditions ]-------------------> 0
*
* @var int
* @var int $state
*/
$state = 0;
@ -98,8 +98,10 @@ class JoinKeyword extends Component
}
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
*
* @var Token $token
*/
$token = $list->tokens[$list->idx];

View File

@ -107,13 +107,15 @@ class Key extends Component
*
* 2 ---------------------[ options ]---------------------> 3
*
* @var int
* @var int $state
*/
$state = 0;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
*
* @var Token $token
*/
$token = $list->tokens[$list->idx];

View File

@ -65,8 +65,10 @@ class Limit extends Component
$offset = false;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
*
* @var Token $token
*/
$token = $list->tokens[$list->idx];

View File

@ -57,24 +57,28 @@ class OptionsArray extends Component
/**
* The ID that will be assigned to duplicate options.
*
* @var int $lastAssignedId
*/
$lastAssignedId = count($options) + 1;
/**
* The option that was processed last time.
*
* @var array $lastOption
*/
$lastOption = null;
/**
* The index of the option that was processed last time.
*
* @var int $lastOptionId
*/
$lastOptionId = 0;
/**
* Counts brackets.
*
* @var int $brackets
*/
$brackets = 0;
@ -90,13 +94,15 @@ class OptionsArray extends Component
*
* 2 ----------------------[ value ]----------------------> 0
*
* @var int
* @var int $state
*/
$state = 0;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
*
* @var Token $token
*/
$token = $list->tokens[$list->idx];

View File

@ -74,13 +74,15 @@ class OrderKeyword extends Component
* 1 ------------------------[ , ]------------------------> 0
* 1 -------------------[ ASC / DESC ]--------------------> 1
*
* @var int
* @var int $state
*/
$state = 0;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
*
* @var Token $token
*/
$token = $list->tokens[$list->idx];

View File

@ -73,15 +73,17 @@ class ParameterDefinition extends Component
* 2 -------------------[ data type ]--------------------> 3
*
* 3 ------------------------[ , ]-----------------------> 1
* 3 ------------------------[ ) ]-----------------------> -1
* 3 ------------------------[ ) ]-----------------------> (END)
*
* @var int
* @var int $state
*/
$state = 0;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
*
* @var Token $token
*/
$token = $list->tokens[$list->idx];

View File

@ -0,0 +1,219 @@
<?php
/**
* Parses the create definition of a partition.
*
* Used for parsing `CREATE TABLE` statement.
*
* @package SqlParser
* @subpackage Components
*/
namespace SqlParser\Components;
use SqlParser\Context;
use SqlParser\Component;
use SqlParser\Parser;
use SqlParser\Token;
use SqlParser\TokensList;
/**
* Parses the create definition of a partition.
*
* Used for parsing `CREATE TABLE` statement.
*
* @category Components
* @package SqlParser
* @subpackage Components
* @author Dan Ungureanu <udan1107@gmail.com>
* @license http://opensource.org/licenses/GPL-2.0 GNU Public License
*/
class PartitionDefinition extends Component
{
/**
* All field options.
*
* @var array
*/
public static $OPTIONS = array(
'STORAGE' => array(1, 'var'),
'STORAGE ENGINE' => array(1, 'var'),
'COMMENT' => array(2, 'var'),
'DATA DIRECTORY' => array(3, 'var'),
'INDEX DIRECTORY' => array(4, 'var'),
'MAX_ROWS' => array(5, 'var'),
'MIN_ROWS' => array(6, 'var'),
'TABLESPACE' => array(7, 'var'),
'NODEGROUP' => array(8, 'var'),
);
/**
* Whether this entry is a subpartition or a partition.
*
* @var bool
*/
public $isSubpartition;
/**
* The name of this partition.
*
* @var string
*/
public $name;
/**
* The type of this partition (what follows the `VALUES` keyword).
*
* @var string
*/
public $type;
/**
* The expression used to defined this partition.
*
* @var Expression
*/
public $expr;
/**
* The subpartitions of this partition.
*
* @var PartitionDefinition[]
*/
public $subpartitions;
/**
* The options of this field.
*
* @var OptionsArray
*/
public $options;
/**
* @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 PartitionDefinition
*/
public static function parse(Parser $parser, TokensList $list, array $options = array())
{
$ret = new PartitionDefinition();
/**
* The state of the parser.
*
* Below are the states of the parser.
*
* 0 -------------[ PARTITION | SUBPARTITION ]------------> 1
*
* 1 -----------------------[ name ]----------------------> 2
*
* 2 ----------------------[ VALUES ]---------------------> 3
*
* 3 ---------------------[ LESS THAN ]-------------------> 4
* 3 ------------------------[ IN ]-----------------------> 4
*
* 4 -----------------------[ expr ]----------------------> 5
*
* 5 ----------------------[ options ]--------------------> 6
*
* 6 ------------------[ subpartitions ]------------------> (END)
*
* @var int $state
*/
$state = 0;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
*
* @var Token $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;
}
if ($state === 0) {
$ret->isSubpartition = ($token->type === Token::TYPE_KEYWORD) && ($token->value === 'SUBPARTITION');
$state = 1;
} elseif ($state === 1) {
$ret->name = $token->value;
$state = $ret->isSubpartition ? 5 : 2;
} elseif ($state === 2) {
$state = 3;
} elseif ($state === 3) {
$ret->type = $token->value;
$state = 4;
} elseif ($state === 4) {
if ($token->value === 'MAXVALUE') {
$ret->expr = $token->value;
} else {
$ret->expr = Expression::parse(
$parser,
$list,
array(
'bracketsDelimited' => true,
'noAlias' => true,
)
);
}
$state = 5;
} elseif ($state === 5) {
$ret->options = OptionsArray::parse($parser, $list, static::$OPTIONS);
$state = 6;
} elseif ($state === 6) {
if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '(')) {
$ret->subpartitions = ArrayObj::parse(
$parser,
$list,
array(
'type' => 'SqlParser\\Components\\PartitionDefinition'
)
);
++$list->idx;
}
break;
}
}
--$list->idx;
return $ret;
}
/**
* @param PartitionDefinition|PartitionDefinition[] $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 "(\n" . implode(",\n", $ret) . "\n)";
} else {
if ($component->isSubpartition) {
return 'SUBPARTITION ' . $component->name;
} else {
$subpartitions = empty($component->subpartitions)
? '' : ' ' . PartitionDefinition::build($component->subpartitions);
return 'PARTITION ' . $component->name
. ' VALUES ' . $component->type . ' ' . $component->expr
. $subpartitions;
}
}
}
}

View File

@ -92,15 +92,17 @@ class Reference extends Component
*
* 1 ---------------------[ columns ]--------------------> 2
*
* 2 ---------------------[ options ]--------------------> -1
* 2 ---------------------[ options ]--------------------> (END)
*
* @var int
* @var int $state
*/
$state = 0;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
*
* @var Token $token
*/
$token = $list->tokens[$list->idx];

View File

@ -44,7 +44,7 @@ class RenameOperation extends Component
* @param TokensList $list The list of tokens that are being parsed.
* @param array $options Parameters for parsing.
*
* @return RenameOperation
* @return RenameOperation[]
*/
public static function parse(Parser $parser, TokensList $list, array $options = array())
{
@ -64,15 +64,17 @@ class RenameOperation extends Component
* 2 ---------------------[ old name ]--------------------> 3
*
* 3 ------------------------[ , ]------------------------> 0
* 3 -----------------------[ else ]----------------------> -1
* 3 -----------------------[ else ]----------------------> (END)
*
* @var int
* @var int $state
*/
$state = 0;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
*
* @var Token $token
*/
$token = $list->tokens[$list->idx];
@ -157,4 +159,23 @@ class RenameOperation extends Component
--$list->idx;
return $ret;
}
/**
* @param RenameOperation $component The component to be built.
*
* @return string
*/
public static function build($component)
{
if (is_array($component)) {
$values = array();
foreach ($component as $c) {
$values[] = static::build($c);
}
return implode(', ', $values);
} else {
return Expression::build($component->old) . ' TO '
. Expression::build($component->new);
}
}
}

View File

@ -62,13 +62,15 @@ class SetOperation extends Component
* 1 ------------------------[ , ]------------------------> 0
* 1 ----------------------[ value ]----------------------> 1
*
* @var int
* @var int $state
*/
$state = 0;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
*
* @var Token $token
*/
$token = $list->tokens[$list->idx];

View File

@ -450,6 +450,7 @@ abstract class Context
/**
* The number of replaces done by `preg_replace`.
* This actually represents whether a new context was generated or not.
*
* @var int $count
*/
$count = 0;

View File

@ -54,9 +54,8 @@ class ContextMySql50000 extends Context
'EVENTS' => 1, 'FAULTS' => 1, 'FIELDS' => 1, 'GLOBAL' => 1, 'GRANTS' => 1,
'IMPORT' => 1, 'INNODB' => 1, 'ISSUER' => 1, 'LEAVES' => 1, 'MASTER' => 1,
'MEDIUM' => 1, 'MEMORY' => 1, 'MODIFY' => 1, 'OFFSET' => 1, 'RELOAD' => 1,
'REPAIR' => 1, 'RESUME' => 1, 'ROLLUP' => 1, 'SERIAL' => 1, 'SIGNED' => 1,
'SIMPLE' => 1, 'SOUNDS' => 1, 'SOURCE' => 1, 'STATUS' => 1, 'STRING' => 1,
'TABLES' => 1,
'REPAIR' => 1, 'RESUME' => 1, 'ROLLUP' => 1, 'SIGNED' => 1, 'SIMPLE' => 1,
'SOUNDS' => 1, 'SOURCE' => 1, 'STATUS' => 1, 'STRING' => 1, 'TABLES' => 1,
'AGAINST' => 1, 'CHANGED' => 1, 'COLUMNS' => 1, 'COMMENT' => 1, 'COMPACT' => 1,
'CONTEXT' => 1, 'DEFINER' => 1, 'DISABLE' => 1, 'DISCARD' => 1, 'DYNAMIC' => 1,
'ENGINES' => 1, 'EXECUTE' => 1, 'HANDLER' => 1, 'INDEXES' => 1, 'INVOKER' => 1,
@ -77,8 +76,8 @@ class ContextMySql50000 extends Context
'VARIABLES' => 1,
'BERKELEYDB' => 1, 'COMPRESSED' => 1, 'CONCURRENT' => 1, 'CONNECTION' => 1,
'CONSISTENT' => 1, 'DEALLOCATE' => 1, 'IDENTIFIED' => 1, 'MASTER_SSL' => 1,
'NDBCLUSTER' => 1, 'PERSISTENT' => 1, 'PRIVILEGES' => 1, 'REPEATABLE' => 1,
'ROW_FORMAT' => 1, 'SQL_THREAD' => 1, 'TABLESPACE' => 1,
'NDBCLUSTER' => 1, 'PARTITIONS' => 1, 'PERSISTENT' => 1, 'PRIVILEGES' => 1,
'REPEATABLE' => 1, 'ROW_FORMAT' => 1, 'SQL_THREAD' => 1, 'TABLESPACE' => 1,
'FRAC_SECOND' => 1, 'MASTER_HOST' => 1, 'MASTER_PORT' => 1, 'MASTER_USER' => 1,
'PROCESSLIST' => 1, 'RAID_CHUNKS' => 1, 'REPLICATION' => 1, 'SQL_TSI_DAY' => 1,
'TRANSACTION' => 1, 'UNCOMMITTED' => 1,
@ -86,7 +85,7 @@ class ContextMySql50000 extends Context
'SQL_NO_CACHE' => 1, 'SQL_TSI_HOUR' => 1, 'SQL_TSI_WEEK' => 1,
'SQL_TSI_YEAR' => 1,
'INSERT_METHOD' => 1, 'MASTER_SSL_CA' => 1, 'RELAY_LOG_POS' => 1,
'SQL_TSI_MONTH' => 1,
'SQL_TSI_MONTH' => 1, 'SUBPARTITIONS' => 1,
'AUTO_INCREMENT' => 1, 'AVG_ROW_LENGTH' => 1, 'MASTER_LOG_POS' => 1,
'MASTER_SSL_KEY' => 1, 'RAID_CHUNKSIZE' => 1, 'RELAY_LOG_FILE' => 1,
'SQL_TSI_MINUTE' => 1, 'SQL_TSI_SECOND' => 1, 'USER_RESOURCES' => 1,
@ -145,12 +144,16 @@ class ContextMySql50000 extends Context
'GROUP BY' => 7, 'NOT NULL' => 7, 'ORDER BY' => 7, 'SET NULL' => 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,
'LESS THAN' => 7, 'NO ACTION' => 7, 'ON DELETE' => 7, 'ON UPDATE' => 7,
'INNER JOIN' => 7, 'LINEAR KEY' => 7, 'NO RELEASE' => 7, 'OR REPLACE' => 7,
'RIGHT JOIN' => 7,
'LINEAR HASH' => 7,
'AND NO CHAIN' => 7, 'FOR EACH ROW' => 7, 'PARTITION BY' => 7,
'SQL SECURITY' => 7,
'CHARACTER SET' => 7, 'IF NOT EXISTS' => 7,
'DATA DIRECTORY' => 7,
'DEFAULT CHARSET' => 7, 'DEFAULT COLLATE' => 7, 'INDEX DIRECTORY' => 7,
'SUBPARTITION BY' => 7,
'GENERATED ALWAYS' => 7,
'START TRANSACTION' => 7,
'SELECT TRANSACTION' => 7,
@ -160,6 +163,7 @@ class ContextMySql50000 extends Context
'XML' => 9,
'ENUM' => 9, 'TEXT' => 9,
'ARRAY' => 9,
'SERIAL' => 9,
'BOOLEAN' => 9,
'DATETIME' => 9, 'MULTISET' => 9,

View File

@ -57,9 +57,9 @@ class ContextMySql50100 extends Context
'IMPORT' => 1, 'INNODB' => 1, 'ISSUER' => 1, 'LEAVES' => 1, 'MASTER' => 1,
'MEDIUM' => 1, 'MEMORY' => 1, 'MODIFY' => 1, 'OFFSET' => 1, 'PARSER' => 1,
'PLUGIN' => 1, 'RELOAD' => 1, 'REMOVE' => 1, 'REPAIR' => 1, 'RESUME' => 1,
'ROLLUP' => 1, 'SERIAL' => 1, 'SERVER' => 1, 'SIGNED' => 1, 'SIMPLE' => 1,
'SOCKET' => 1, 'SONAME' => 1, 'SOUNDS' => 1, 'SOURCE' => 1, 'STARTS' => 1,
'STATUS' => 1, 'STRING' => 1, 'TABLES' => 1,
'ROLLUP' => 1, 'SERVER' => 1, 'SIGNED' => 1, 'SIMPLE' => 1, 'SOCKET' => 1,
'SONAME' => 1, 'SOUNDS' => 1, 'SOURCE' => 1, 'STARTS' => 1, 'STATUS' => 1,
'STRING' => 1, 'TABLES' => 1,
'AGAINST' => 1, 'AUTHORS' => 1, 'CHANGED' => 1, 'COLUMNS' => 1, 'COMMENT' => 1,
'COMPACT' => 1, 'CONTEXT' => 1, 'DEFINER' => 1, 'DISABLE' => 1, 'DISCARD' => 1,
'DYNAMIC' => 1, 'ENGINES' => 1, 'EXECUTE' => 1, 'HANDLER' => 1, 'INDEXES' => 1,
@ -157,12 +157,16 @@ class ContextMySql50100 extends Context
'GROUP BY' => 7, 'NOT NULL' => 7, 'ORDER BY' => 7, 'SET NULL' => 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,
'LESS THAN' => 7, 'NO ACTION' => 7, 'ON DELETE' => 7, 'ON UPDATE' => 7,
'INNER JOIN' => 7, 'LINEAR KEY' => 7, 'NO RELEASE' => 7, 'OR REPLACE' => 7,
'RIGHT JOIN' => 7,
'LINEAR HASH' => 7,
'AND NO CHAIN' => 7, 'FOR EACH ROW' => 7, 'PARTITION BY' => 7,
'SQL SECURITY' => 7,
'CHARACTER SET' => 7, 'IF NOT EXISTS' => 7,
'DATA DIRECTORY' => 7,
'DEFAULT CHARSET' => 7, 'DEFAULT COLLATE' => 7, 'INDEX DIRECTORY' => 7,
'SUBPARTITION BY' => 7,
'GENERATED ALWAYS' => 7,
'START TRANSACTION' => 7,
'SELECT TRANSACTION' => 7,
@ -172,6 +176,7 @@ class ContextMySql50100 extends Context
'XML' => 9,
'ENUM' => 9, 'TEXT' => 9,
'ARRAY' => 9,
'SERIAL' => 9,
'BOOLEAN' => 9,
'DATETIME' => 9, 'MULTISET' => 9,

View File

@ -57,9 +57,9 @@ class ContextMySql50500 extends Context
'IMPORT' => 1, 'INNODB' => 1, 'ISSUER' => 1, 'LEAVES' => 1, 'MASTER' => 1,
'MEDIUM' => 1, 'MEMORY' => 1, 'MODIFY' => 1, 'OFFSET' => 1, 'PARSER' => 1,
'PLUGIN' => 1, 'RELOAD' => 1, 'REMOVE' => 1, 'REPAIR' => 1, 'RESUME' => 1,
'ROLLUP' => 1, 'SERIAL' => 1, 'SERVER' => 1, 'SIGNED' => 1, 'SIMPLE' => 1,
'SOCKET' => 1, 'SONAME' => 1, 'SOUNDS' => 1, 'SOURCE' => 1, 'STARTS' => 1,
'STATUS' => 1, 'STRING' => 1, 'TABLES' => 1,
'ROLLUP' => 1, 'SERVER' => 1, 'SIGNED' => 1, 'SIMPLE' => 1, 'SOCKET' => 1,
'SONAME' => 1, 'SOUNDS' => 1, 'SOURCE' => 1, 'STARTS' => 1, 'STATUS' => 1,
'STRING' => 1, 'TABLES' => 1,
'AGAINST' => 1, 'AUTHORS' => 1, 'CHANGED' => 1, 'COLUMNS' => 1, 'COMMENT' => 1,
'COMPACT' => 1, 'CONTEXT' => 1, 'DEFINER' => 1, 'DISABLE' => 1, 'DISCARD' => 1,
'DYNAMIC' => 1, 'ENGINES' => 1, 'EXECUTE' => 1, 'GENERAL' => 1, 'HANDLER' => 1,
@ -162,12 +162,16 @@ class ContextMySql50500 extends Context
'GROUP BY' => 7, 'NOT NULL' => 7, 'ORDER BY' => 7, 'SET NULL' => 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,
'LESS THAN' => 7, 'NO ACTION' => 7, 'ON DELETE' => 7, 'ON UPDATE' => 7,
'INNER JOIN' => 7, 'LINEAR KEY' => 7, 'NO RELEASE' => 7, 'OR REPLACE' => 7,
'RIGHT JOIN' => 7,
'LINEAR HASH' => 7,
'AND NO CHAIN' => 7, 'FOR EACH ROW' => 7, 'PARTITION BY' => 7,
'SQL SECURITY' => 7,
'CHARACTER SET' => 7, 'IF NOT EXISTS' => 7,
'DATA DIRECTORY' => 7,
'DEFAULT CHARSET' => 7, 'DEFAULT COLLATE' => 7, 'INDEX DIRECTORY' => 7,
'SUBPARTITION BY' => 7,
'GENERATED ALWAYS' => 7,
'START TRANSACTION' => 7,
'SELECT TRANSACTION' => 7,
@ -177,6 +181,7 @@ class ContextMySql50500 extends Context
'XML' => 9,
'ENUM' => 9, 'TEXT' => 9,
'ARRAY' => 9,
'SERIAL' => 9,
'BOOLEAN' => 9,
'DATETIME' => 9, 'MULTISET' => 9,

View File

@ -57,9 +57,9 @@ class ContextMySql50600 extends Context
'GLOBAL' => 1, 'GRANTS' => 1, 'IMPORT' => 1, 'ISSUER' => 1, 'LEAVES' => 1,
'MASTER' => 1, 'MEDIUM' => 1, 'MEMORY' => 1, 'MODIFY' => 1, 'NUMBER' => 1,
'OFFSET' => 1, 'PARSER' => 1, 'PLUGIN' => 1, 'RELOAD' => 1, 'REMOVE' => 1,
'REPAIR' => 1, 'RESUME' => 1, 'ROLLUP' => 1, 'SERIAL' => 1, 'SERVER' => 1,
'SIGNED' => 1, 'SIMPLE' => 1, 'SOCKET' => 1, 'SONAME' => 1, 'SOUNDS' => 1,
'SOURCE' => 1, 'STARTS' => 1, 'STATUS' => 1, 'STRING' => 1, 'TABLES' => 1,
'REPAIR' => 1, 'RESUME' => 1, 'ROLLUP' => 1, 'SERVER' => 1, 'SIGNED' => 1,
'SIMPLE' => 1, 'SOCKET' => 1, 'SONAME' => 1, 'SOUNDS' => 1, 'SOURCE' => 1,
'STARTS' => 1, 'STATUS' => 1, 'STRING' => 1, 'TABLES' => 1,
'AGAINST' => 1, 'ANALYSE' => 1, 'AUTHORS' => 1, 'CHANGED' => 1, 'COLUMNS' => 1,
'COMMENT' => 1, 'COMPACT' => 1, 'CONTEXT' => 1, 'CURRENT' => 1, 'DEFINER' => 1,
'DISABLE' => 1, 'DISCARD' => 1, 'DYNAMIC' => 1, 'ENGINES' => 1, 'EXECUTE' => 1,
@ -167,12 +167,16 @@ class ContextMySql50600 extends Context
'GROUP BY' => 7, 'NOT NULL' => 7, 'ORDER BY' => 7, 'SET NULL' => 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,
'LESS THAN' => 7, 'NO ACTION' => 7, 'ON DELETE' => 7, 'ON UPDATE' => 7,
'INNER JOIN' => 7, 'LINEAR KEY' => 7, 'NO RELEASE' => 7, 'OR REPLACE' => 7,
'RIGHT JOIN' => 7,
'LINEAR HASH' => 7,
'AND NO CHAIN' => 7, 'FOR EACH ROW' => 7, 'PARTITION BY' => 7,
'SQL SECURITY' => 7,
'CHARACTER SET' => 7, 'IF NOT EXISTS' => 7,
'DATA DIRECTORY' => 7,
'DEFAULT CHARSET' => 7, 'DEFAULT COLLATE' => 7, 'INDEX DIRECTORY' => 7,
'SUBPARTITION BY' => 7,
'GENERATED ALWAYS' => 7,
'START TRANSACTION' => 7,
'SELECT TRANSACTION' => 7,
@ -182,6 +186,7 @@ class ContextMySql50600 extends Context
'XML' => 9,
'ENUM' => 9, 'TEXT' => 9,
'ARRAY' => 9,
'SERIAL' => 9,
'BOOLEAN' => 9,
'DATETIME' => 9, 'MULTISET' => 9,

View File

@ -58,9 +58,9 @@ class ContextMySql50700 extends Context
'ISSUER' => 1, 'LEAVES' => 1, 'MASTER' => 1, 'MEDIUM' => 1, 'MEMORY' => 1,
'MODIFY' => 1, 'NUMBER' => 1, 'OFFSET' => 1, 'PARSER' => 1, 'PLUGIN' => 1,
'RELOAD' => 1, 'REMOVE' => 1, 'REPAIR' => 1, 'RESUME' => 1, 'ROLLUP' => 1,
'SERIAL' => 1, 'SERVER' => 1, 'SIGNED' => 1, 'SIMPLE' => 1, 'SOCKET' => 1,
'SONAME' => 1, 'SOUNDS' => 1, 'SOURCE' => 1, 'STARTS' => 1, 'STATUS' => 1,
'STRING' => 1, 'TABLES' => 1,
'SERVER' => 1, 'SIGNED' => 1, 'SIMPLE' => 1, 'SOCKET' => 1, 'SONAME' => 1,
'SOUNDS' => 1, 'SOURCE' => 1, 'STARTS' => 1, 'STATUS' => 1, 'STRING' => 1,
'TABLES' => 1,
'ACCOUNT' => 1, 'AGAINST' => 1, 'ANALYSE' => 1, 'CHANGED' => 1, 'CHANNEL' => 1,
'COLUMNS' => 1, 'COMMENT' => 1, 'COMPACT' => 1, 'CONTEXT' => 1, 'CURRENT' => 1,
'DEFINER' => 1, 'DISABLE' => 1, 'DISCARD' => 1, 'DYNAMIC' => 1, 'ENGINES' => 1,
@ -175,12 +175,16 @@ class ContextMySql50700 extends Context
'GROUP BY' => 7, 'NOT NULL' => 7, 'ORDER BY' => 7, 'SET NULL' => 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,
'LESS THAN' => 7, 'NO ACTION' => 7, 'ON DELETE' => 7, 'ON UPDATE' => 7,
'INNER JOIN' => 7, 'LINEAR KEY' => 7, 'NO RELEASE' => 7, 'OR REPLACE' => 7,
'RIGHT JOIN' => 7,
'LINEAR HASH' => 7,
'AND NO CHAIN' => 7, 'FOR EACH ROW' => 7, 'PARTITION BY' => 7,
'SQL SECURITY' => 7,
'CHARACTER SET' => 7, 'IF NOT EXISTS' => 7,
'DATA DIRECTORY' => 7,
'DEFAULT CHARSET' => 7, 'DEFAULT COLLATE' => 7, 'INDEX DIRECTORY' => 7,
'SUBPARTITION BY' => 7,
'GENERATED ALWAYS' => 7,
'START TRANSACTION' => 7,
'SELECT TRANSACTION' => 7,
@ -190,6 +194,7 @@ class ContextMySql50700 extends Context
'XML' => 9,
'ENUM' => 9, 'TEXT' => 9,
'ARRAY' => 9,
'SERIAL' => 9,
'BOOLEAN' => 9,
'DATETIME' => 9, 'MULTISET' => 9,

View File

@ -42,6 +42,7 @@ namespace SqlParser {
/**
* 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);
@ -242,13 +243,16 @@ namespace SqlParser {
/**
* Last processed token.
*
* @var Token $lastToken
*/
$lastToken = null;
for ($this->last = 0, $lastIdx = 0; $this->last < $this->len; $lastIdx = ++$this->last) {
/**
* The new token.
*
* @var Token $token
*/
$token = null;
@ -392,23 +396,27 @@ namespace SqlParser {
/**
* 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;
/**
* 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])) {
@ -426,6 +434,7 @@ namespace SqlParser {
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.
@ -448,12 +457,14 @@ namespace SqlParser {
/**
* 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;
@ -799,7 +810,11 @@ namespace SqlParser {
}
if ($flags & Token::FLAG_SYMBOL_VARIABLE) {
++$this->last;
if ($this->str[++$this->last] === '@') {
// This is a system variable (e.g. `@@hostname`).
$token .= $this->str[$this->last++];
$flags |= Token::FLAG_SYMBOL_SYSTEM;
}
} else {
$token = '';
}

View File

@ -113,153 +113,158 @@ namespace SqlParser {
*/
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',
// This is not a proper keyword and was added here to help the
// formatter.
'PARTITION BY' => array(),
'SUBPARTITION BY' => array(),
// This is not a proper keyword and was added here to help the
// builder.
'_OPTIONS' => array(
'class' => 'SqlParser\\Components\\OptionsArray',
'field' => 'options',
),
'UNION' => array(
'class' => 'SqlParser\\Components\\UnionKeyword',
'field' => 'union',
),
// This is used only for building.
'UNION' => array(
'class' => 'SqlParser\\Components\\UnionKeyword',
'field' => 'union',
// Actual clause parsers.
'ALTER' => array(
'class' => 'SqlParser\\Components\\Expression',
'field' => 'table',
'options' => array('skipColumn' => true),
),
'ALTER' => array(
'class' => 'SqlParser\\Components\\Expression',
'field' => 'table',
'options' => array('skipColumn' => true),
'ANALYZE' => array(
'class' => 'SqlParser\\Components\\ExpressionArray',
'field' => 'tables',
'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),
),
'BACKUP' => array(
'class' => 'SqlParser\\Components\\ExpressionArray',
'field' => 'tables',
'options' => array('skipColumn' => true),
'CALL' => array(
'class' => 'SqlParser\\Components\\FunctionCall',
'field' => 'call',
),
'CALL' => array(
'class' => 'SqlParser\\Components\\FunctionCall',
'field' => 'call',
'CHECK' => array(
'class' => 'SqlParser\\Components\\ExpressionArray',
'field' => 'tables',
'options' => array('skipColumn' => true),
),
'CHECK' => array(
'class' => 'SqlParser\\Components\\ExpressionArray',
'field' => 'tables',
'options' => array('skipColumn' => true),
'CHECKSUM' => 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),
),
'DROP' => array(
'class' => 'SqlParser\\Components\\ExpressionArray',
'field' => 'fields',
'options' => array('skipColumn' => true),
'FROM' => array(
'class' => 'SqlParser\\Components\\ExpressionArray',
'field' => 'from',
'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',
),
'GROUP BY' => array(
'class' => 'SqlParser\\Components\\OrderKeyword',
'field' => 'group',
'HAVING' => array(
'class' => 'SqlParser\\Components\\Condition',
'field' => 'having',
),
'HAVING' => array(
'class' => 'SqlParser\\Components\\Condition',
'field' => 'having',
'INTO' => array(
'class' => 'SqlParser\\Components\\IntoKeyword',
'field' => 'into',
),
'INTO' => array(
'class' => 'SqlParser\\Components\\IntoKeyword',
'field' => 'into',
'JOIN' => array(
'class' => 'SqlParser\\Components\\JoinKeyword',
'field' => 'join',
),
'JOIN' => array(
'class' => 'SqlParser\\Components\\JoinKeyword',
'field' => 'join',
'LEFT 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',
),
'RIGHT JOIN' => array(
'class' => 'SqlParser\\Components\\JoinKeyword',
'field' => 'join',
'INNER 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',
),
'FULL JOIN' => array(
'class' => 'SqlParser\\Components\\JoinKeyword',
'field' => 'join',
'LIMIT' => array(
'class' => 'SqlParser\\Components\\Limit',
'field' => 'limit',
),
'LIMIT' => array(
'class' => 'SqlParser\\Components\\Limit',
'field' => 'limit',
'OPTIMIZE' => array(
'class' => 'SqlParser\\Components\\ExpressionArray',
'field' => 'tables',
'options' => array('skipColumn' => true),
),
'OPTIMIZE' => array(
'class' => 'SqlParser\\Components\\ExpressionArray',
'field' => 'tables',
'options' => array('skipColumn' => true),
'ORDER BY' => array(
'class' => 'SqlParser\\Components\\OrderKeyword',
'field' => 'order',
),
'ORDER BY' => array(
'class' => 'SqlParser\\Components\\OrderKeyword',
'field' => 'order',
'PARTITION' => array(
'class' => 'SqlParser\\Components\\ArrayObj',
'field' => 'partition',
),
'PARTITION' => array(
'class' => 'SqlParser\\Components\\ArrayObj',
'field' => 'partition',
'PROCEDURE' => array(
'class' => 'SqlParser\\Components\\FunctionCall',
'field' => 'procedure',
),
'PROCEDURE' => array(
'class' => 'SqlParser\\Components\\FunctionCall',
'field' => 'procedure',
'RENAME' => array(
'class' => 'SqlParser\\Components\\RenameOperation',
'field' => 'renames',
),
'RENAME' => array(
'class' => 'SqlParser\\Components\\RenameOperation',
'field' => 'renames',
'REPAIR' => array(
'class' => 'SqlParser\\Components\\ExpressionArray',
'field' => 'tables',
'options' => array('skipColumn' => true),
),
'REPAIR' => array(
'class' => 'SqlParser\\Components\\ExpressionArray',
'field' => 'tables',
'options' => array('skipColumn' => true),
'RESTORE' => 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',
),
'SET' => array(
'class' => 'SqlParser\\Components\\SetOperation',
'field' => 'set',
'SELECT' => array(
'class' => 'SqlParser\\Components\\ExpressionArray',
'field' => 'expr',
),
'SELECT' => array(
'class' => 'SqlParser\\Components\\ExpressionArray',
'field' => 'expr',
'TRUNCATE' => array(
'class' => 'SqlParser\\Components\\Expression',
'field' => 'table',
'options' => array('skipColumn' => true),
),
'TRUNCATE' => array(
'class' => 'SqlParser\\Components\\Expression',
'field' => 'table',
'options' => array('skipColumn' => true),
'UPDATE' => array(
'class' => 'SqlParser\\Components\\ExpressionArray',
'field' => 'tables',
'options' => array('skipColumn' => true),
),
'UPDATE' => array(
'class' => 'SqlParser\\Components\\ExpressionArray',
'field' => 'tables',
'options' => array('skipColumn' => true),
'VALUE' => array(
'class' => 'SqlParser\\Components\\Array2d',
'field' => 'values',
),
'VALUE' => array(
'class' => 'SqlParser\\Components\\Array2d',
'field' => 'values',
'VALUES' => array(
'class' => 'SqlParser\\Components\\Array2d',
'field' => 'values',
),
'VALUES' => array(
'class' => 'SqlParser\\Components\\Array2d',
'field' => 'values',
),
'WHERE' => array(
'class' => 'SqlParser\\Components\\Condition',
'field' => 'where',
'WHERE' => array(
'class' => 'SqlParser\\Components\\Condition',
'field' => 'where',
),
);
@ -304,7 +309,7 @@ namespace SqlParser {
* Constructor.
*
* @param string|UtfString|TokensList $list The list of tokens to be parsed.
* @param bool $strict Whether strict mode should be enabled or not.
* @param bool $strict Whether strict mode should be enabled or not.
*/
public function __construct($list = null, $strict = false)
{
@ -332,30 +337,35 @@ namespace SqlParser {
/**
* Last transaction.
*
* @var TransactionStatement $lastTransaction
*/
$lastTransaction = null;
/**
* Last parsed statement.
*
* @var Statement $lastStatement
*/
$lastStatement = null;
/**
* Whether a union is parsed or not.
*
* @var bool $inUnion
*/
$inUnion = false;
/**
* 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;
@ -364,6 +374,7 @@ namespace SqlParser {
/**
* Token parsed at this moment.
*
* @var Token $token
*/
$token = $list->tokens[$list->idx];
@ -419,12 +430,14 @@ namespace SqlParser {
/**
* 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);
@ -445,13 +458,16 @@ namespace SqlParser {
&& ($lastStatement instanceof SelectStatement)
&& ($statement instanceof SelectStatement)
) {
/**
* This SELECT statement.
*
* @var SelectStatement $statement
*/
/**
* Last SELECT statement.
*
* @var SelectStatement $lastStatement
*/
$lastStatement->union[] = $statement;

View File

@ -103,6 +103,7 @@ abstract class Statement
{
/**
* Query to be returned.
*
* @var string $query
*/
$query = '';
@ -111,12 +112,14 @@ abstract class Statement
/**
* The name of the clause.
*
* @var string $name
*/
$name = $clause[0];
/**
* The type of the clause.
*
* @see self::$CLAUSES
* @var int $type
*/
@ -124,6 +127,7 @@ abstract class Statement
/**
* The builder (parser) of this clause.
*
* @var Component $class
*/
$class = Parser::$KEYWORD_PARSERS[$name]['class'];
@ -131,6 +135,7 @@ abstract class Statement
/**
* The name of the field that is used as source for the builder.
* Same field is used to store the result of parsing.
*
* @var string $field
*/
$field = Parser::$KEYWORD_PARSERS[$name]['field'];
@ -167,7 +172,8 @@ abstract class Statement
/**
* Array containing all list of clauses parsed.
* This is used to check for duplicates.
* @var array
*
* @var array $parsedClauses
*/
$parsedClauses = array();
@ -178,13 +184,16 @@ abstract class Statement
* Whether options were parsed or not.
* For statements that do not have any options this is set to `true` by
* default.
*
* @var bool $parsedOptions
*/
$parsedOptions = empty(static::$OPTIONS);
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
*
* @var Token $token
*/
$token = $list->tokens[$list->idx];
@ -213,18 +222,21 @@ abstract class Statement
/**
* The name of the class that is used for parsing.
*
* @var Component $class
*/
$class = null;
/**
* The name of the field where the result of the parsing is stored.
*
* @var string $field
*/
$field = null;
/**
* Parser's options.
*
* @var array $options
*/
$options = array();

View File

@ -89,13 +89,15 @@ class AlterStatement extends Statement
*
* 1 -------------------------[ , ]-----------------------> 0
*
* @var int
* @var int $state
*/
$state = 0;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
*
* @var Token $token
*/
$token = $list->tokens[$list->idx];

View File

@ -15,6 +15,7 @@ use SqlParser\TokensList;
use SqlParser\Components\ArrayObj;
use SqlParser\Components\DataType;
use SqlParser\Components\CreateDefinition;
use SqlParser\Components\PartitionDefinition;
use SqlParser\Components\Expression;
use SqlParser\Components\OptionsArray;
use SqlParser\Components\ParameterDefinition;
@ -173,6 +174,41 @@ class CreateStatement extends Statement
*/
public $fields;
/**
* Expression used for partitioning.
*
* @var string
*/
public $partitionBy;
/**
* The number of partitions.
*
* @var int
*/
public $partitionsNum;
/**
* Expression used for subpartitioning.
*
* @var string
*/
public $subpartitionBy;
/**
* The number of subpartitions.
*
* @var int
*/
public $subpartitionsNum;
/**
* The partition of the new table.
*
* @var PartitionDefinition[]
*/
public $partitions;
/**
* If `CREATE TRIGGER` the name of the table.
*
@ -229,11 +265,30 @@ class CreateStatement extends Statement
. Expression::build($this->name) . ' '
. OptionsArray::build($this->entityOptions);
} elseif ($this->options->has('TABLE')) {
$partition = '';
if (!empty($this->partitionBy)) {
$partition .= "\nPARTITION BY " . $this->partitionBy;
}
if (!empty($this->partitionsNum)) {
$partition .= "\nPARTITIONS " . $this->partitionsNum;
}
if (!empty($this->subpartitionBy)) {
$partition .= "\nSUBPARTITION BY " . $this->subpartitionBy;
}
if (!empty($this->subpartitionsNum)) {
$partition .= "\nSUBPARTITIONS " . $this->subpartitionsNum;
}
if (!empty($this->partitions)) {
$partition .= "\n" . PartitionDefinition::build($this->partitions);
}
return 'CREATE '
. OptionsArray::build($this->options) . ' '
. Expression::build($this->name) . ' '
. $fields
. OptionsArray::build($this->entityOptions);
. OptionsArray::build($this->entityOptions)
. $partition;
} elseif ($this->options->has('VIEW')) {
return 'CREATE '
. OptionsArray::build($this->options) . ' '
@ -318,6 +373,100 @@ class CreateStatement extends Statement
$list,
static::$TABLE_OPTIONS
);
/**
* The field that is being filled (`partitionBy` or
* `subpartitionBy`).
*
* @var string $field
*/
$field = null;
/**
* The number of brackets. `false` means no bracket was found
* previously. At least one bracket is required to validate the
* expression.
*
* @var int|bool $brackets
*/
$brackets = false;
/*
* Handles partitions.
*/
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
*
* @var Token $token
*/
$token = $list->tokens[$list->idx];
// End of statement.
if ($token->type === Token::TYPE_DELIMITER) {
break;
}
// Skipping comments.
if ($token->type === Token::TYPE_COMMENT) {
continue;
}
if (($token->type === Token::TYPE_KEYWORD) && ($token->value === 'PARTITION BY')) {
$field = 'partitionBy';
$brackets = false;
} elseif (($token->type === Token::TYPE_KEYWORD) && ($token->value === 'SUBPARTITION BY')) {
$field = 'subpartitionBy';
$brackets = false;
} elseif (($token->type === Token::TYPE_KEYWORD) && ($token->value === 'PARTITIONS')) {
$token = $list->getNextOfType(Token::TYPE_NUMBER);
--$list->idx; // `getNextOfType` also advances one position.
$this->partitionsNum = $token->value;
} elseif (($token->type === Token::TYPE_KEYWORD) && ($token->value === 'SUBPARTITIONS')) {
$token = $list->getNextOfType(Token::TYPE_NUMBER);
--$list->idx; // `getNextOfType` also advances one position.
$this->subpartitionsNum = $token->value;
} elseif (!empty($field)) {
/*
* Handling the content of `PARTITION BY` and `SUBPARTITION BY`.
*/
// Counting brackets.
if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '(')) {
// This is used instead of `++$brackets` because,
// initially, `$brackets` is `false` cannot be
// incremented.
$brackets = $brackets + 1;
} elseif (($token->type === Token::TYPE_OPERATOR) && ($token->value === ')')) {
--$brackets;
}
// Building the expression used for partitioning.
$this->$field .= ($token->type === Token::TYPE_WHITESPACE) ? ' ' : $token->token;
// Last bracket was read, the expression ended.
// Comparing with `0` and not `false`, because `false` means
// that no bracket was found and at least one must is
// required.
if ($brackets === 0) {
$this->$field = trim($this->$field);
$field = null;
}
} elseif (($token->type === Token::TYPE_OPERATOR) && ($token->value === '(')) {
if (!empty($this->partitionBy)) {
$this->partitions = ArrayObj::parse(
$parser,
$list,
array(
'type' => 'SqlParser\\Components\\PartitionDefinition'
)
);
}
break;
}
}
} elseif (($this->options->has('PROCEDURE'))
|| ($this->options->has('FUNCTION'))
) {

View File

@ -157,6 +157,7 @@ class Token
const FLAG_SYMBOL_VARIABLE = 1;
const FLAG_SYMBOL_BACKTICK = 2;
const FLAG_SYMBOL_USER = 4;
const FLAG_SYMBOL_SYSTEM = 8;
/**
* The token it its raw string representation.
@ -256,7 +257,12 @@ class Token
if ((isset($str[0])) && ($str[0] === '@')) {
// `mb_strlen($str)` must be used instead of `null` because
// in PHP 5.3- the `null` parameter isn't handled correctly.
$str = mb_substr($str, 1, mb_strlen($str), 'UTF-8');
$str = mb_substr(
$str,
((!empty($str[1])) && ($str[1] === '@')) ? 2 : 1,
mb_strlen($str),
'UTF-8'
);
}
if ((isset($str[0])) && (($str[0] === '`')
|| ($str[0] === '"') || ($str[0] === '\''))

View File

@ -91,14 +91,17 @@ class BufferedQuery
// Merges specified options with defaults.
$this->options = array_merge(
array(
/**
* The starting delimiter.
*
* @var string
*/
'delimiter' => ';',
/**
* Whether `DELIMITER` statements should be parsed.
*
* @var bool
*/
'parse_delimiter' => false,
@ -106,6 +109,7 @@ class BufferedQuery
/**
* Whether a delimiter should be added at the end of the
* statement.
*
* @var bool
*/
'add_delimiter' => false,
@ -156,6 +160,7 @@ class BufferedQuery
/**
* The length of the buffer.
*
* @var int $len
*/
$len = strlen($this->query);

View File

@ -41,8 +41,12 @@ class Formatter
*/
public static $INLINE_CLAUSES = array(
'CREATE' => true,
'PROCEDURE' => true,
'LIMIT' => true,
'PARTITION BY' => true,
'PARTITION' => true,
'PROCEDURE' => true,
'SUBPARTITION BY' => true,
'VALUES' => true,
);
/**
@ -58,6 +62,7 @@ class Formatter
/**
* The format of the result.
*
* @var string The type ('text', 'cli' or 'html')
*/
'type' => php_sapi_name() == 'cli' ? 'cli' : 'text',
@ -65,24 +70,28 @@ class Formatter
/**
* The line ending used.
* By default, for text this is "\n" and for HTML this is "<br/>".
*
* @var string
*/
'line_ending' => $this->options['type'] == 'html' ? '<br/>' : "\n",
/**
* The string used for indentation.
*
* @var string
*/
'indentation' => " ",
/**
* Whether comments should be removed or not.
*
* @var bool
*/
'remove_comments' => false,
/**
* Whether each clause should be on a new line.
*
* @var bool
*/
'clause_newline' => true,
@ -90,12 +99,14 @@ class Formatter
/**
* Whether each part should be on a new line.
* Parts are delimited by brackets and commas.
*
* @var bool
*/
'parts_newline' => true,
/**
* Whether each part of each clause should be indented.
*
* @var bool
*/
'indent_parts' => true,
@ -103,6 +114,7 @@ class Formatter
/**
* The styles used for HTML formatting.
* array($type, $flags, $span, $callback)
*
* @var array[]
*/
'formats' => array(
@ -176,24 +188,28 @@ class Formatter
/**
* The query to be returned.
*
* @var string $ret
*/
$ret = '';
/**
* The indentation level.
*
* @var int $indent
*/
$indent = 0;
/**
* Whether the line ended.
*
* @var bool $lineEnded
*/
$lineEnded = false;
/**
* The name of the last clause.
*
* @var string $lastClause
*/
$lastClause = '';
@ -201,6 +217,7 @@ class Formatter
/**
* A stack that keeps track of the indentation level every time a new
* block is found.
*
* @var array $blocksIndentation
*/
$blocksIndentation = array();
@ -208,18 +225,21 @@ class Formatter
/**
* A stack that keeps track of the line endings every time a new block
* is found.
*
* @var array $blocksLineEndings
*/
$blocksLineEndings = array();
/**
* Whether clause's options were formatted.
*
* @var bool $formattedOptions
*/
$formattedOptions = false;
/**
* Previously parsed token.
*
* @var Token $prev
*/
$prev = null;
@ -227,6 +247,7 @@ class Formatter
/**
* Comments are being formatted separately to maintain the whitespaces
* before and after them.
*
* @var string $comment
*/
$comment = '';
@ -239,6 +260,7 @@ class Formatter
/**
* Token parsed at this moment.
*
* @var Token $curr
*/
$curr = $list->tokens[$list->idx];
@ -368,7 +390,7 @@ class Formatter
if (!((($prev->type === Token::TYPE_OPERATOR) && (($prev->value === '.') || ($prev->value === '(')))
// No space after . (
|| (($curr->type === Token::TYPE_OPERATOR) && (($curr->value === '.') || ($curr->value === ',') || ($curr->value === '(') || ($curr->value === ')')))
// No space before . , )
// No space before . , ( )
|| (($curr->type === Token::TYPE_DELIMITER)) && (mb_strlen($curr->value, 'UTF-8') < 2))
// A space after delimiters that are longer than 2 characters.
|| ($prev->value === 'DELIMITER')
@ -461,13 +483,15 @@ class Formatter
* This counter starts at one because by the time this function called,
* the list already advanced one position and the opening bracket was
* already parsed.
* @var int
*
* @var int $count
*/
$count = 1;
/**
* The length of this group.
* @var int
*
* @var int $length
*/
$length = 0;

View File

@ -47,7 +47,8 @@ class Misc
/**
* Expressions that may contain aliases.
* These are extracted from `FROM` and `JOIN` keywords.
* @var Expression[]
*
* @var Expression[] $expressions
*/
$expressions = $statement->from;

View File

@ -510,6 +510,7 @@ class Query
/**
* The index of the current clause.
*
* @var int $currIdx
*/
$currIdx = 0;
@ -517,18 +518,21 @@ class Query
/**
* The count of brackets.
* We keep track of them so we won't insert the clause in a subquery.
*
* @var int $brackets
*/
$brackets = 0;
/**
* The string to be returned.
*
* @var string $ret
*/
$ret = '';
/**
* The clauses of this type of statement and their index.
*
* @var array $clauses
*/
$clauses = array_flip(array_keys($statement::$CLAUSES));
@ -543,18 +547,21 @@ class Query
/**
* Lexer used for lexing the clause.
*
* @var Lexer $lexer
*/
$lexer = new Lexer($clause);
/**
* The type of this clause.
*
* @var string $clauseType
*/
$clauseType = $lexer->list->getNextOfType(Token::TYPE_KEYWORD)->value;
/**
* The index of this clause.
*
* @var int $clauseIdx
*/
$clauseIdx = $clauses[$clauseType];
@ -680,6 +687,7 @@ class Query
/**
* Value to be returned.
*
* @var string $ret
*/
$ret = '';
@ -730,13 +738,15 @@ class Query
/**
* Whether a full statement was found.
* @var bool
*
* @var bool $fullStatement
*/
$fullStatement = false;
/**
* The first full statement.
* @var string
*
* @var string $statement
*/
$statement = '';