Initial commit

This commit is contained in:
2022-11-21 09:47:28 +01:00
commit 76cec83d26
11652 changed files with 1980467 additions and 0 deletions

View File

@@ -0,0 +1,265 @@
<?php
/**
* Parses an alter operation.
*
* @package SqlParser
* @subpackage Components
*/
namespace SqlParser\Components;
use SqlParser\Component;
use SqlParser\Parser;
use SqlParser\Token;
use SqlParser\TokensList;
/**
* Parses an alter operation.
*
* @category Components
* @package SqlParser
* @subpackage Components
* @license https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
*/
class AlterOperation extends Component
{
/**
* All database options
*
* @var array
*/
public static $DB_OPTIONS = array(
'CHARACTER SET' => array(1, 'var'),
'CHARSET' => array(1, 'var'),
'DEFAULT CHARACTER SET' => array(1, 'var'),
'DEFAULT CHARSET' => array(1, 'var'),
'UPGRADE' => array(1, 'var'),
'COLLATE' => array(2, 'var'),
'DEFAULT COLLATE' => array(2, 'var'),
);
/**
* All table options
*
* @var array
*/
public static $TABLE_OPTIONS = array(
'ENGINE' => array(1, 'var='),
'AUTO_INCREMENT' => array(1, 'var='),
'AVG_ROW_LENGTH' => array(1, 'var'),
'MAX_ROWS' => array(1, 'var'),
'ROW_FORMAT' => array(1, 'var'),
'COMMENT' => array(1, 'var'),
'ADD' => 1,
'ALTER' => 1,
'ANALYZE' => 1,
'CHANGE' => 1,
'CHECK' => 1,
'COALESCE' => 1,
'CONVERT' => 1,
'DISABLE' => 1,
'DISCARD' => 1,
'DROP' => 1,
'ENABLE' => 1,
'IMPORT' => 1,
'MODIFY' => 1,
'OPTIMIZE' => 1,
'ORDER' => 1,
'PARTITION' => 1,
'REBUILD' => 1,
'REMOVE' => 1,
'RENAME' => 1,
'REORGANIZE' => 1,
'REPAIR' => 1,
'UPGRADE' => 1,
'COLUMN' => 2,
'CONSTRAINT' => 2,
'DEFAULT' => 2,
'TO' => 2,
'BY' => 2,
'FOREIGN' => 2,
'FULLTEXT' => 2,
'KEY' => 2,
'KEYS' => 2,
'PARTITIONING' => 2,
'PRIMARY KEY' => 2,
'SPATIAL' => 2,
'TABLESPACE' => 2,
'INDEX' => 2,
);
/**
* All view options
*
* @var array
*/
public static $VIEW_OPTIONS = array(
'AS' => 1,
);
/**
* Options of this operation.
*
* @var OptionsArray
*/
public $options;
/**
* The altered field.
*
* @var Expression
*/
public $field;
/**
* Unparsed tokens.
*
* @var Token[]|string
*/
public $unknown = array();
/**
* @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 AlterOperation
*/
public static function parse(Parser $parser, TokensList $list, array $options = array())
{
$ret = new AlterOperation();
/**
* Counts brackets.
*
* @var int $brackets
*/
$brackets = 0;
/**
* The state of the parser.
*
* Below are the states of the parser.
*
* 0 ---------------------[ options ]---------------------> 1
*
* 1 ----------------------[ field ]----------------------> 2
*
* 2 -------------------------[ , ]-----------------------> 0
*
* @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 comments.
if ($token->type === Token::TYPE_COMMENT) {
continue;
}
// Skipping whitespaces.
if ($token->type === Token::TYPE_WHITESPACE) {
if ($state === 2) {
// When parsing the unknown part, the whitespaces are
// included to not break anything.
$ret->unknown[] = $token;
}
continue;
}
if ($state === 0) {
$ret->options = OptionsArray::parse($parser, $list, $options);
if ($ret->options->has('AS')) {
for (; $list->idx < $list->count; ++$list->idx) {
if ($list->tokens[$list->idx]->type === Token::TYPE_DELIMITER) {
break;
}
$ret->unknown[] = $list->tokens[$list->idx];
}
break;
}
$state = 1;
} elseif ($state === 1) {
$ret->field = Expression::parse(
$parser,
$list,
array(
'breakOnAlias' => true,
'parseField' => 'column',
)
);
if ($ret->field === null) {
// No field was read. We go back one token so the next
// iteration will parse the same token, but in state 2.
--$list->idx;
}
$state = 2;
} elseif ($state === 2) {
if ($token->type === Token::TYPE_OPERATOR) {
if ($token->value === '(') {
++$brackets;
} elseif ($token->value === ')') {
--$brackets;
} elseif (($token->value === ',') && ($brackets === 0)) {
break;
}
} elseif (!empty(Parser::$STATEMENT_PARSERS[$token->value])) {
// We have reached the end of ALTER operation and suddenly found
// a start to new statement, but have not find a delimiter between them
if (! ($token->value == 'SET' && $list->tokens[$list->idx - 1]->value == 'CHARACTER')) {
$parser->error(
__('A new statement was found, but no delimiter between it and the previous one.'),
$token
);
break;
}
}
$ret->unknown[] = $token;
}
}
if ($ret->options->isEmpty()) {
$parser->error(
__('Unrecognized alter operation.'),
$list->tokens[$list->idx]
);
}
--$list->idx;
return $ret;
}
/**
* @param AlterOperation $component The component to be built.
* @param array $options Parameters for building.
*
* @return string
*/
public static function build($component, array $options = array())
{
$ret = $component->options . ' ';
if ((isset($component->field)) && ($component->field !== '')) {
$ret .= $component->field . ' ';
}
$ret .= TokensList::build($component->unknown);
return $ret;
}
}

View File

@@ -0,0 +1,133 @@
<?php
/**
* `VALUES` keyword parser.
*
* @package SqlParser
* @subpackage Components
*/
namespace SqlParser\Components;
use SqlParser\Component;
use SqlParser\Parser;
use SqlParser\Token;
use SqlParser\TokensList;
/**
* `VALUES` keyword parser.
*
* @category Keywords
* @package SqlParser
* @subpackage Components
* @license https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
*/
class Array2d extends Component
{
/**
* @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 ArrayObj[]
*/
public static function parse(Parser $parser, TokensList $list, array $options = array())
{
$ret = array();
/**
* The number of values in each set.
*
* @var int $count
*/
$count = -1;
/**
* The state of the parser.
*
* Below are the states of the parser.
*
* 0 ----------------------[ array ]----------------------> 1
*
* 1 ------------------------[ , ]------------------------> 0
* 1 -----------------------[ else ]----------------------> (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;
}
// No keyword is expected.
if (($token->type === Token::TYPE_KEYWORD) && ($token->flags & Token::FLAG_KEYWORD_RESERVED)) {
break;
}
if ($state === 0) {
if ($token->value === '(') {
$arr = ArrayObj::parse($parser, $list, $options);
$arrCount = count($arr->values);
if ($count === -1) {
$count = $arrCount;
} elseif ($arrCount != $count) {
$parser->error(
sprintf(
__('%1$d values were expected, but found %2$d.'),
$count,
$arrCount
),
$token
);
}
$ret[] = $arr;
$state = 1;
} else {
break;
}
} elseif ($state === 1) {
if ($token->value === ',') {
$state = 0;
} else {
break;
}
}
}
if ($state === 0) {
$parser->error(
__('An opening bracket followed by a set of values was expected.'),
$list->tokens[$list->idx]
);
}
--$list->idx;
return $ret;
}
/**
* @param ArrayObj[] $component The component to be built.
* @param array $options Parameters for building.
*
* @return string
*/
public static function build($component, array $options = array())
{
return ArrayObj::build($component);
}
}

View File

@@ -0,0 +1,193 @@
<?php
/**
* Parses an array.
*
* @package SqlParser
* @subpackage Components
*/
namespace SqlParser\Components;
use SqlParser\Component;
use SqlParser\Parser;
use SqlParser\Token;
use SqlParser\TokensList;
/**
* Parses an array.
*
* @category Components
* @package SqlParser
* @subpackage Components
* @license https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
*/
class ArrayObj extends Component
{
/**
* 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.
*
* @var array
*/
public $values = array();
/**
* Constructor.
*
* @param array $raw The unprocessed values.
* @param array $values The processed values.
*/
public function __construct(array $raw = array(), array $values = array())
{
$this->raw = $raw;
$this->values = $values;
}
/**
* @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 ArrayObj|Component[]
*/
public static function parse(Parser $parser, TokensList $list, array $options = array())
{
$ret = empty($options['type']) ? new ArrayObj() : array();
/**
* The last raw expression.
*
* @var string $lastRaw
*/
$lastRaw = '';
/**
* The last value.
*
* @var string $lastValue
*/
$lastValue = '';
/**
* Counts brackets.
*
* @var int $brackets
*/
$brackets = 0;
/**
* Last separator (bracket or comma).
*
* @var boolean $isCommaLast
*/
$isCommaLast = false;
for (; $list->idx < $list->count; ++$list->idx) {
/**
* 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)
) {
$lastRaw .= $token->token;
$lastValue = trim($lastValue) . ' ';
continue;
}
if (($brackets === 0)
&& (($token->type !== Token::TYPE_OPERATOR)
|| ($token->value !== '('))
) {
$parser->error(__('An opening bracket was expected.'), $token);
break;
}
if ($token->type === Token::TYPE_OPERATOR) {
if ($token->value === '(') {
if (++$brackets === 1) { // 1 is the base level.
continue;
}
} elseif ($token->value === ')') {
if (--$brackets === 0) { // Array ended.
break;
}
} elseif ($token->value === ',') {
if ($brackets === 1) {
$isCommaLast = true;
if (empty($options['type'])) {
$ret->raw[] = trim($lastRaw);
$ret->values[] = trim($lastValue);
$lastRaw = $lastValue = '';
}
}
continue;
}
}
if (empty($options['type'])) {
$lastRaw .= $token->token;
$lastValue .= $token->value;
} else {
$ret[] = $options['type']::parse(
$parser,
$list,
empty($options['typeOptions']) ? array() : $options['typeOptions']
);
}
}
// Handling last element.
//
// This is treated differently to treat the following cases:
//
// => array()
// (,) => array('', '')
// () => array()
// (a,) => array('a', '')
// (a) => array('a')
//
$lastRaw = trim($lastRaw);
if ((empty($options['type']))
&& ((strlen($lastRaw) > 0) || ($isCommaLast))
) {
$ret->raw[] = $lastRaw;
$ret->values[] = trim($lastValue);
}
return $ret;
}
/**
* @param ArrayObj|ArrayObj[] $component The component to be built.
* @param array $options Parameters for building.
*
* @return string
*/
public static function build($component, array $options = array())
{
if (is_array($component)) {
return implode(', ', $component);
} elseif (!empty($component->raw)) {
return '(' . implode(', ', $component->raw) . ')';
} else {
return '(' . implode(', ', $component->values) . ')';
}
}
}

View File

@@ -0,0 +1,249 @@
<?php
/**
* Parses a reference to a CASE expression
*
* @package SqlParser
* @subpackage Components
*/
namespace SqlParser\Components;
use SqlParser\Component;
use SqlParser\Parser;
use SqlParser\Token;
use SqlParser\TokensList;
/**
* Parses a reference to a CASE expression
*
* @category Components
* @package SqlParser
* @subpackage Components
* @license https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
*/
class CaseExpression extends Component
{
/**
* The value to be compared
*
* @var Expression
*/
public $value;
/**
* The conditions in WHEN clauses
*
* @var array
*/
public $conditions;
/**
* The results matching with the WHEN clauses
*
* @var array
*/
public $results;
/**
* The values to be compared against
*
* @var array
*/
public $compare_values;
/**
* The result in ELSE section of expr
*
* @var array
*/
public $else_result;
/**
* Constructor.
*
*/
public function __construct()
{
}
/**
*
* @param Parser $parser The parser that serves as context.
* @param TokensList $list The list of tokens that are being parsed.
*
* @return Expression
*/
public static function parse(Parser $parser, TokensList $list, array $options = array())
{
$ret = new CaseExpression();
/**
* State of parser
*
* @var int $parser
*/
$state = 0;
/**
* Syntax type (type 0 or type 1)
*
* @var int $type
*/
$type = 0;
++$list->idx; // Skip 'CASE'
for (; $list->idx < $list->count; ++$list->idx) {
/**
* Token parsed at this moment.
*
* @var Token $token
*/
$token = $list->tokens[$list->idx];
// Skipping whitespaces and comments.
if (($token->type === Token::TYPE_WHITESPACE)
|| ($token->type === Token::TYPE_COMMENT)
) {
continue;
}
if ($state === 0) {
if ($token->type === Token::TYPE_KEYWORD
&& $token->value === 'WHEN'
) {
++$list->idx; // Skip 'WHEN'
$new_condition = Condition::parse($parser, $list);
$type = 1;
$state = 1;
$ret->conditions[] = $new_condition;
} elseif ($token->type === Token::TYPE_KEYWORD
&& $token->value === 'ELSE'
) {
++$list->idx; // Skip 'ELSE'
$ret->else_result = Expression::parse($parser, $list);
$state = 0; // last clause of CASE expression
} elseif ($token->type === Token::TYPE_KEYWORD
&& ($token->value === 'END'
|| $token->value === 'end')
) {
$state = 3; // end of CASE expression
++$list->idx;
break;
} elseif ($token->type === Token::TYPE_KEYWORD) {
$parser->error(__('Unexpected keyword.'), $token);
break;
} else {
$ret->value = Expression::parse($parser, $list);
$type = 0;
$state = 1;
}
} elseif ($state === 1) {
if ($type === 0) {
if ($token->type === Token::TYPE_KEYWORD
&& $token->value === 'WHEN'
) {
++$list->idx; // Skip 'WHEN'
$new_value = Expression::parse($parser, $list);
$state = 2;
$ret->compare_values[] = $new_value;
} elseif ($token->type === Token::TYPE_KEYWORD
&& $token->value === 'ELSE'
) {
++$list->idx; // Skip 'ELSE'
$ret->else_result = Expression::parse($parser, $list);
$state = 0; // last clause of CASE expression
} elseif ($token->type === Token::TYPE_KEYWORD
&& ($token->value === 'END'
|| $token->value === 'end')
) {
$state = 3; // end of CASE expression
++$list->idx;
break;
} elseif ($token->type === Token::TYPE_KEYWORD) {
$parser->error(__('Unexpected keyword.'), $token);
break;
}
} else {
if ($token->type === Token::TYPE_KEYWORD
&& $token->value === 'THEN'
) {
++$list->idx; // Skip 'THEN'
$new_result = Expression::parse($parser, $list);
$state = 0;
$ret->results[] = $new_result;
} elseif ($token->type === Token::TYPE_KEYWORD) {
$parser->error(__('Unexpected keyword.'), $token);
break;
}
}
} elseif ($state === 2) {
if ($type === 0) {
if ($token->type === Token::TYPE_KEYWORD
&& $token->value === 'THEN'
) {
++$list->idx; // Skip 'THEN'
$new_result = Expression::parse($parser, $list);
$ret->results[] = $new_result;
$state = 1;
} elseif ($token->type === Token::TYPE_KEYWORD) {
$parser->error(__('Unexpected keyword.'), $token);
break;
}
}
}
}
if ($state !== 3) {
$parser->error(
__('Unexpected end of CASE expression'),
$list->tokens[$list->idx - 1]
);
}
--$list->idx;
return $ret;
}
/**
* @param Expression $component The component to be built.
* @param array $options Parameters for building.
*
* @return string
*/
public static function build($component, array $options = array())
{
$ret = 'CASE ';
if (isset($component->value)) {
// Syntax type 0
$ret .= $component->value . ' ';
for (
$i = 0;
$i < count($component->compare_values) && $i < count($component->results);
++$i
) {
$ret .= 'WHEN ' . $component->compare_values[$i] . ' ';
$ret .= 'THEN ' . $component->results[$i] . ' ';
}
} else {
// Syntax type 1
for (
$i = 0;
$i < count($component->conditions) && $i < count($component->results);
++$i
) {
$ret .= 'WHEN ' . Condition::build($component->conditions[$i]) . ' ';
$ret .= 'THEN ' . $component->results[$i] . ' ';
}
}
if (isset($component->else_result)) {
$ret .= 'ELSE ' . $component->else_result . ' ';
}
$ret .= 'END';
return $ret;
}
}

View File

@@ -0,0 +1,231 @@
<?php
/**
* `WHERE` keyword parser.
*
* @package SqlParser
* @subpackage Components
*/
namespace SqlParser\Components;
use SqlParser\Component;
use SqlParser\Parser;
use SqlParser\Token;
use SqlParser\TokensList;
/**
* `WHERE` keyword parser.
*
* @category Keywords
* @package SqlParser
* @subpackage Components
* @license https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
*/
class Condition extends Component
{
/**
* Logical operators that can be used to delimit expressions.
*
* @var array
*/
public static $DELIMITERS = array('&&', '||', 'AND', 'OR', 'XOR');
/**
* List of allowed reserved keywords in conditions.
*
* @var array
*/
public static $ALLOWED_KEYWORDS = array(
'ALL' => 1,
'AND' => 1,
'BETWEEN' => 1,
'EXISTS' => 1,
'IF' => 1,
'IN' => 1,
'INTERVAL' => 1,
'IS' => 1,
'LIKE' => 1,
'MATCH' => 1,
'NOT IN' => 1,
'NOT NULL' => 1,
'NOT' => 1,
'NULL' => 1,
'OR' => 1,
'REGEXP' => 1,
'RLIKE' => 1,
'XOR' => 1,
);
/**
* Identifiers recognized.
*
* @var array
*/
public $identifiers = array();
/**
* Whether this component is an operator.
*
* @var bool
*/
public $isOperator = false;
/**
* The condition.
*
* @var string
*/
public $expr;
/**
* Constructor.
*
* @param string $expr The condition or the operator.
*/
public function __construct($expr = null)
{
$this->expr = trim($expr);
}
/**
* @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 Condition[]
*/
public static function parse(Parser $parser, TokensList $list, array $options = array())
{
$ret = array();
$expr = new Condition();
/**
* Counts brackets.
*
* @var int $brackets
*/
$brackets = 0;
/**
* Whether there was a `BETWEEN` keyword before or not.
*
* It is required to keep track of them because their structure contains
* the keyword `AND`, which is also an operator that delimits
* expressions.
*
* @var bool $betweenBefore
*/
$betweenBefore = false;
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_COMMENT) {
continue;
}
// Replacing all whitespaces (new lines, tabs, etc.) with a single
// space character.
if ($token->type === Token::TYPE_WHITESPACE) {
$expr->expr .= ' ';
continue;
}
// Conditions are delimited by logical operators.
if (in_array($token->value, static::$DELIMITERS, true)) {
if (($betweenBefore) && ($token->value === 'AND')) {
// The syntax of keyword `BETWEEN` is hard-coded.
$betweenBefore = false;
} else {
// The expression ended.
$expr->expr = trim($expr->expr);
if (!empty($expr->expr)) {
$ret[] = $expr;
}
// Adding the operator.
$expr = new Condition($token->value);
$expr->isOperator = true;
$ret[] = $expr;
// Preparing to parse another condition.
$expr = new Condition();
continue;
}
}
if (($token->type === Token::TYPE_KEYWORD)
&& ($token->flags & Token::FLAG_KEYWORD_RESERVED)
&& !($token->flags & Token::FLAG_KEYWORD_FUNCTION)
) {
if ($token->value === 'BETWEEN') {
$betweenBefore = true;
}
if (($brackets === 0) && (empty(static::$ALLOWED_KEYWORDS[$token->value]))) {
break;
}
}
if ($token->type === Token::TYPE_OPERATOR) {
if ($token->value === '(') {
++$brackets;
} elseif ($token->value === ')') {
if ($brackets == 0) {
break;
}
--$brackets;
}
}
$expr->expr .= $token->token;
if (($token->type === Token::TYPE_NONE)
|| (($token->type === Token::TYPE_KEYWORD)
&& (!($token->flags & Token::FLAG_KEYWORD_RESERVED)))
|| ($token->type === Token::TYPE_STRING)
|| ($token->type === Token::TYPE_SYMBOL)
) {
if (!in_array($token->value, $expr->identifiers)) {
$expr->identifiers[] = $token->value;
}
}
}
// Last iteration was not processed.
$expr->expr = trim($expr->expr);
if (!empty($expr->expr)) {
$ret[] = $expr;
}
--$list->idx;
return $ret;
}
/**
* @param Condition[] $component The component to be built.
* @param array $options Parameters for building.
*
* @return string
*/
public static function build($component, array $options = array())
{
if (is_array($component)) {
return implode(' ', $component);
} else {
return $component->expr;
}
}
}

View File

@@ -0,0 +1,339 @@
<?php
/**
* Parses the create definition of a column or a key.
*
* 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 column or a key.
*
* Used for parsing `CREATE TABLE` statement.
*
* @category Components
* @package SqlParser
* @subpackage Components
* @license https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
*/
class CreateDefinition extends Component
{
/**
* All field options.
*
* @var array
*/
public static $FIELD_OPTIONS = array(
// Tells the `OptionsArray` to not sort the options.
// See the note below.
'_UNSORTED' => true,
'NOT NULL' => 1,
'NULL' => 1,
'DEFAULT' => array(2, 'expr', array('breakOnAlias' => true)),
'AUTO_INCREMENT' => 3,
'PRIMARY' => 4,
'PRIMARY KEY' => 4,
'UNIQUE' => 4,
'UNIQUE KEY' => 4,
'COMMENT' => array(5, 'var'),
'COLUMN_FORMAT' => array(6, 'var'),
'ON UPDATE' => array(7, 'expr'),
// Generated columns options.
'GENERATED ALWAYS' => 8,
'AS' => array(9, 'expr', array('parenthesesDelimited' => true)),
'VIRTUAL' => 10,
'PERSISTENT' => 11,
'STORED' => 11,
// Common entries.
//
// NOTE: Some of the common options are not in the same order which
// causes troubles when checking if the options are in the right order.
// I should find a way to define multiple sets of options and make the
// parser select the right set.
//
// 'UNIQUE' => 4,
// 'UNIQUE KEY' => 4,
// 'COMMENT' => array(5, 'var'),
// 'NOT NULL' => 1,
// 'NULL' => 1,
// 'PRIMARY' => 4,
// 'PRIMARY KEY' => 4,
);
/**
* The name of the new column.
*
* @var string
*/
public $name;
/**
* Whether this field is a constraint or not.
*
* @var bool
*/
public $isConstraint;
/**
* The data type of thew new column.
*
* @var DataType
*/
public $type;
/**
* The key.
*
* @var Key
*/
public $key;
/**
* The table that is referenced.
*
* @var Reference
*/
public $references;
/**
* The options of this field.
*
* @var OptionsArray
*/
public $options;
/**
* Constructor.
*
* @param string $name The name of the field.
* @param OptionsArray $options The options of this field.
* @param DataType|Key $type The data type of this field or the key.
* @param bool $isConstraint Whether this field is a constraint or not.
* @param Reference $references References.
*/
public function __construct(
$name = null,
$options = null,
$type = null,
$isConstraint = false,
$references = null
) {
$this->name = $name;
$this->options = $options;
if ($type instanceof DataType) {
$this->type = $type;
} elseif ($type instanceof Key) {
$this->key = $type;
$this->isConstraint = $isConstraint;
$this->references = $references;
}
}
/**
* @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 CreateDefinition[]
*/
public static function parse(Parser $parser, TokensList $list, array $options = array())
{
$ret = array();
$expr = new CreateDefinition();
/**
* The state of the parser.
*
* Below are the states of the parser.
*
* 0 -----------------------[ ( ]------------------------> 1
*
* 1 --------------------[ CONSTRAINT ]------------------> 1
* 1 -----------------------[ key ]----------------------> 2
* 1 -------------[ constraint / column name ]-----------> 2
*
* 2 --------------------[ data type ]-------------------> 3
*
* 3 ---------------------[ options ]--------------------> 4
*
* 4 --------------------[ REFERENCES ]------------------> 4
*
* 5 ------------------------[ , ]-----------------------> 1
* 5 ------------------------[ ) ]-----------------------> 6 (-1)
*
* @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) {
if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '(')) {
$state = 1;
} else {
$parser->error(
__('An opening bracket was expected.'),
$token
);
break;
}
} elseif ($state === 1) {
if (($token->type === Token::TYPE_KEYWORD) && ($token->value === 'CONSTRAINT')) {
$expr->isConstraint = true;
} elseif (($token->type === Token::TYPE_KEYWORD) && ($token->flags & Token::FLAG_KEYWORD_KEY)) {
$expr->key = Key::parse($parser, $list);
$state = 4;
} elseif ($token->type === Token::TYPE_SYMBOL || $token->type === Token::TYPE_NONE) {
$expr->name = $token->value;
if (!$expr->isConstraint) {
$state = 2;
}
} else if ($token->type === Token::TYPE_KEYWORD) {
if ($token->flags & Token::FLAG_KEYWORD_RESERVED) {
// Reserved keywords can't be used
// as field names without backquotes
$parser->error(
__('A symbol name was expected! '
. 'A reserved keyword can not be used '
. 'as a column name without backquotes.'
),
$token
);
return $ret;
} else {
// Non-reserved keywords are allowed without backquotes
$expr->name = $token->value;
$state = 2;
}
} else {
$parser->error(
__('A symbol name was expected!'),
$token
);
return $ret;
}
} elseif ($state === 2) {
$expr->type = DataType::parse($parser, $list);
$state = 3;
} elseif ($state === 3) {
$expr->options = OptionsArray::parse($parser, $list, static::$FIELD_OPTIONS);
$state = 4;
} elseif ($state === 4) {
if (($token->type === Token::TYPE_KEYWORD) && ($token->value === 'REFERENCES')) {
++$list->idx; // Skipping keyword 'REFERENCES'.
$expr->references = Reference::parse($parser, $list);
} else {
--$list->idx;
}
$state = 5;
} elseif ($state === 5) {
if ((!empty($expr->type)) || (!empty($expr->key))) {
$ret[] = $expr;
}
$expr = new CreateDefinition();
if ($token->value === ',') {
$state = 1;
} elseif ($token->value === ')') {
$state = 6;
++$list->idx;
break;
} else {
$parser->error(
__('A comma or a closing bracket was expected.'),
$token
);
$state = 0;
break;
}
}
}
// Last iteration was not saved.
if ((!empty($expr->type)) || (!empty($expr->key))) {
$ret[] = $expr;
}
if (($state !== 0) && ($state !== 6)) {
$parser->error(
__('A closing bracket was expected.'),
$list->tokens[$list->idx - 1]
);
}
--$list->idx;
return $ret;
}
/**
* @param CreateDefinition|CreateDefinition[] $component The component to be built.
* @param array $options Parameters for building.
*
* @return string
*/
public static function build($component, array $options = array())
{
if (is_array($component)) {
return "(\n " . implode(",\n ", $component) . "\n)";
} else {
$tmp = '';
if ($component->isConstraint) {
$tmp .= 'CONSTRAINT ';
}
if ((isset($component->name)) && ($component->name !== '')) {
$tmp .= Context::escape($component->name) . ' ';
}
if (!empty($component->type)) {
$tmp .= DataType::build(
$component->type,
array('lowercase' => true)
) . ' ';
}
if (!empty($component->key)) {
$tmp .= $component->key . ' ';
}
if (!empty($component->references)) {
$tmp .= 'REFERENCES ' . $component->references . ' ';
}
$tmp .= $component->options;
return trim($tmp);
}
}
}

View File

@@ -0,0 +1,170 @@
<?php
/**
* Parses a data type.
*
* @package SqlParser
* @subpackage Components
*/
namespace SqlParser\Components;
use SqlParser\Component;
use SqlParser\Parser;
use SqlParser\Token;
use SqlParser\TokensList;
/**
* Parses a data type.
*
* @category Components
* @package SqlParser
* @subpackage Components
* @license https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
*/
class DataType extends Component
{
/**
* All data type options.
*
* @var array
*/
public static $DATA_TYPE_OPTIONS = array(
'BINARY' => 1,
'CHARACTER SET' => array(2, 'var'),
'CHARSET' => array(2, 'var'),
'COLLATE' => array(3, 'var'),
'UNSIGNED' => 4,
'ZEROFILL' => 5,
);
/**
* The name of the data type.
*
* @var string
*/
public $name;
/**
* The parameters of this data type.
*
* Some data types have no parameters.
* Numeric types might have parameters for the maximum number of digits,
* precision, etc.
* String types might have parameters for the maximum length stored.
* `ENUM` and `SET` have parameters for possible values.
*
* For more information, check the MySQL manual.
*
* @var array
*/
public $parameters = array();
/**
* The options of this data type.
*
* @var OptionsArray
*/
public $options;
/**
* Constructor.
*
* @param string $name The name of this data type.
* @param array $parameters The parameters (size or possible values).
* @param OptionsArray $options The options of this data type.
*/
public function __construct(
$name = null,
array $parameters = array(),
$options = null
) {
$this->name = $name;
$this->parameters = $parameters;
$this->options = $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 DataType
*/
public static function parse(Parser $parser, TokensList $list, array $options = array())
{
$ret = new DataType();
/**
* The state of the parser.
*
* Below are the states of the parser.
*
* 0 -------------------[ data type ]--------------------> 1
*
* 1 ----------------[ size and options ]----------------> 2
*
* @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];
// Skipping whitespaces and comments.
if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) {
continue;
}
if ($state === 0) {
$ret->name = strtoupper($token->value);
if (($token->type !== Token::TYPE_KEYWORD) || (!($token->flags & Token::FLAG_KEYWORD_DATA_TYPE))) {
$parser->error(__('Unrecognized data type.'), $token);
}
$state = 1;
} elseif ($state === 1) {
if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '(')) {
$parameters = ArrayObj::parse($parser, $list);
++$list->idx;
$ret->parameters = (($ret->name === 'ENUM') || ($ret->name === 'SET')) ?
$parameters->raw : $parameters->values;
}
$ret->options = OptionsArray::parse($parser, $list, static::$DATA_TYPE_OPTIONS);
++$list->idx;
break;
}
}
if (empty($ret->name)) {
return null;
}
--$list->idx;
return $ret;
}
/**
* @param DataType $component The component to be built.
* @param array $options Parameters for building.
*
* @return string
*/
public static function build($component, array $options = array())
{
$name = (empty($options['lowercase'])) ?
$component->name : strtolower($component->name);
$parameters = '';
if (!empty($component->parameters)) {
$parameters = '(' . implode(',', $component->parameters) . ')';
}
return trim($name . $parameters . ' ' . $component->options);
}
}

View File

@@ -0,0 +1,448 @@
<?php
/**
* Parses a reference to an expression (column, table or database name, function
* call, mathematical expression, etc.).
*
* @package SqlParser
* @subpackage Components
*/
namespace SqlParser\Components;
use SqlParser\Context;
use SqlParser\Component;
use SqlParser\Parser;
use SqlParser\Token;
use SqlParser\TokensList;
/**
* Parses a reference to an expression (column, table or database name, function
* call, mathematical expression, etc.).
*
* @category Components
* @package SqlParser
* @subpackage Components
* @license https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
*/
class Expression extends Component
{
/**
* List of allowed reserved keywords in expressions.
*
* @var array
*/
private static $ALLOWED_KEYWORDS = array(
'AS' => 1, 'DUAL' => 1, 'NULL' => 1, 'REGEXP' => 1, 'CASE' => 1
);
/**
* The name of this database.
*
* @var string
*/
public $database;
/**
* The name of this table.
*
* @var string
*/
public $table;
/**
* The name of the column.
*
* @var string
*/
public $column;
/**
* The sub-expression.
*
* @var string
*/
public $expr = '';
/**
* The alias of this expression.
*
* @var string
*/
public $alias;
/**
* The name of the function.
*
* @var mixed
*/
public $function;
/**
* The type of subquery.
*
* @var string
*/
public $subquery;
/**
* Constructor.
*
* Syntax:
* new Expression('expr')
* new Expression('expr', 'alias')
* new Expression('database', 'table', 'column')
* new Expression('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
}
}
/**
* Possible options:
*
* `field`
*
* First field to be filled.
* If this is not specified, it takes the value of `parseField`.
*
* `parseField`
*
* Specifies the type of the field parsed. It may be `database`,
* `table` or `column`. These expressions may not include
* parentheses.
*
* `breakOnAlias`
*
* If not empty, breaks when the alias occurs (it is not included).
*
* `breakOnParentheses`
*
* If not empty, breaks when the first parentheses occurs.
*
* `parenthesesDelimited`
*
* If not empty, breaks after last parentheses occurred.
*
* @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 Expression
*/
public static function parse(Parser $parser, TokensList $list, array $options = array())
{
$ret = new Expression();
/**
* 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 bool $alias
*/
$alias = false;
/**
* Counts brackets.
*
* @var int $brackets
*/
$brackets = 0;
/**
* Keeps track of the last two previous tokens.
*
* @var Token[] $prev
*/
$prev = array(null, null);
// When a field is parsed, no parentheses are expected.
if (!empty($options['parseField'])) {
$options['breakOnParentheses'] = true;
$options['field'] = $options['parseField'];
}
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)
) {
if ($isExpr) {
$ret->expr .= $token->token;
}
continue;
}
if ($token->type === Token::TYPE_KEYWORD) {
if (($brackets > 0) && (empty($ret->subquery))
&& (!empty(Parser::$STATEMENT_PARSERS[$token->value]))
) {
// A `(` was previously found and this keyword is the
// beginning of a statement, so this is a subquery.
$ret->subquery = $token->value;
} elseif (($token->flags & Token::FLAG_KEYWORD_FUNCTION)
&& (empty($options['parseField'])
&& ! $alias)
) {
$isExpr = true;
} elseif (($token->flags & Token::FLAG_KEYWORD_RESERVED)
&& ($brackets === 0)
) {
if (empty(self::$ALLOWED_KEYWORDS[$token->value])) {
// A reserved keyword that is not allowed in the
// expression was found so the expression must have
// ended and a new clause is starting.
break;
}
if ($token->value === 'AS') {
if (!empty($options['breakOnAlias'])) {
break;
}
if ($alias) {
$parser->error(
__('An alias was expected.'),
$token
);
break;
}
$alias = true;
continue;
} elseif ($token->value === 'CASE') {
// For a use of CASE like
// 'SELECT a = CASE .... END, b=1, `id`, ... FROM ...'
$tempCaseExpr = CaseExpression::parse($parser, $list);
$ret->expr .= CaseExpression::build($tempCaseExpr);
$isExpr = true;
continue;
}
$isExpr = true;
} elseif ($brackets === 0 && count($ret->expr) > 0 && ! $alias) {
/* End of expression */
break;
}
}
if (($token->type === Token::TYPE_NUMBER)
|| ($token->type === Token::TYPE_BOOL)
|| (($token->type === Token::TYPE_SYMBOL)
&& ($token->flags & Token::FLAG_SYMBOL_VARIABLE))
|| (($token->type === Token::TYPE_OPERATOR)
&& ($token->value !== '.'))
) {
if (!empty($options['parseField'])) {
break;
}
// Numbers, booleans and operators (except dot) are usually part
// of expressions.
$isExpr = true;
}
if ($token->type === Token::TYPE_OPERATOR) {
if ((!empty($options['breakOnParentheses']))
&& (($token->value === '(') || ($token->value === ')'))
) {
// No brackets were expected.
break;
}
if ($token->value === '(') {
++$brackets;
if ((empty($ret->function)) && ($prev[1] !== null)
&& (($prev[1]->type === Token::TYPE_NONE)
|| ($prev[1]->type === Token::TYPE_SYMBOL)
|| (($prev[1]->type === Token::TYPE_KEYWORD)
&& ($prev[1]->flags & Token::FLAG_KEYWORD_FUNCTION)))
) {
$ret->function = $prev[1]->value;
}
} elseif ($token->value === ')' && $brackets == 0) {
// Not our bracket
break;
} elseif ($token->value === ')') {
--$brackets;
if ($brackets === 0) {
if (!empty($options['parenthesesDelimited'])) {
// The current token is the last bracket, the next
// one will be outside the expression.
$ret->expr .= $token->token;
++$list->idx;
break;
}
} elseif ($brackets < 0) {
// $parser->error(__('Unexpected closing bracket.'), $token);
// $brackets = 0;
break;
}
} elseif ($token->value === ',') {
// Expressions are comma-delimited.
if ($brackets === 0) {
break;
}
}
}
// Saving the previous tokens.
$prev[0] = $prev[1];
$prev[1] = $token;
if ($alias) {
// An alias is expected (the keyword `AS` was previously found).
if (!empty($ret->alias)) {
$parser->error(__('An alias was previously found.'), $token);
break;
}
$ret->alias = $token->value;
$alias = false;
} elseif ($isExpr) {
// Handling aliases.
if (/* (empty($ret->alias)) && */ ($brackets === 0)
&& (($prev[0] === null)
|| ((($prev[0]->type !== Token::TYPE_OPERATOR)
|| ($prev[0]->token === ')'))
&& (($prev[0]->type !== Token::TYPE_KEYWORD)
|| (!($prev[0]->flags & Token::FLAG_KEYWORD_RESERVED)))))
&& (($prev[1]->type === Token::TYPE_STRING)
|| (($prev[1]->type === Token::TYPE_SYMBOL)
&& (!($prev[1]->flags & Token::FLAG_SYMBOL_VARIABLE)))
|| ($prev[1]->type === Token::TYPE_NONE))
) {
if (!empty($ret->alias)) {
$parser->error(__('An alias was previously found.'), $token);
break;
}
$ret->alias = $prev[1]->value;
} else {
$ret->expr .= $token->token;
}
} elseif (!$isExpr) {
if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '.')) {
// Found a `.` which means we expect a column name and
// the column name we parsed is actually the table name
// and the table name is actually a database name.
if ((!empty($ret->database)) || ($dot)) {
$parser->error(__('Unexpected dot.'), $token);
}
$ret->database = $ret->table;
$ret->table = $ret->column;
$ret->column = null;
$dot = true;
$ret->expr .= $token->token;
} else {
$field = empty($options['field']) ? 'column' : $options['field'];
if (empty($ret->$field)) {
$ret->$field = $token->value;
$ret->expr .= $token->token;
$dot = false;
} else {
// No alias is expected.
if (!empty($options['breakOnAlias'])) {
break;
}
if (!empty($ret->alias)) {
$parser->error(__('An alias was previously found.'), $token);
break;
}
$ret->alias = $token->value;
}
}
}
}
if ($alias) {
$parser->error(
__('An alias was expected.'),
$list->tokens[$list->idx - 1]
);
}
// White-spaces might be added at the end.
$ret->expr = trim($ret->expr);
if ($ret->expr === '') {
return null;
}
--$list->idx;
return $ret;
}
/**
* @param Expression|Expression[] $component The component to be built.
* @param array $options Parameters for building.
*
* @return string
*/
public static function build($component, array $options = array())
{
if (is_array($component)) {
return implode($component, ', ');
} else {
if ($component->expr !== '' && !is_null($component->expr)) {
$ret = $component->expr;
} else {
$fields = array();
if ((isset($component->database)) && ($component->database !== '')) {
$fields[] = $component->database;
}
if ((isset($component->table)) && ($component->table !== '')) {
$fields[] = $component->table;
}
if ((isset($component->column)) && ($component->column !== '')) {
$fields[] = $component->column;
}
$ret = implode('.', Context::escape($fields));
}
if (!empty($component->alias)) {
$ret .= ' AS ' . Context::escape($component->alias);
}
return $ret;
}
}
}

View File

@@ -0,0 +1,129 @@
<?php
/**
* Parses a list of expressions delimited by a comma.
*
* @package SqlParser
* @subpackage Components
*/
namespace SqlParser\Components;
use SqlParser\Component;
use SqlParser\Parser;
use SqlParser\Token;
use SqlParser\TokensList;
/**
* Parses a list of expressions delimited by a comma.
*
* @category Keywords
* @package SqlParser
* @subpackage Components
* @license https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
*/
class ExpressionArray extends Component
{
/**
* @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 Expression[]
*/
public static function parse(Parser $parser, TokensList $list, array $options = array())
{
$ret = array();
/**
* The state of the parser.
*
* Below are the states of the parser.
*
* 0 ----------------------[ array ]---------------------> 1
*
* 1 ------------------------[ , ]------------------------> 0
* 1 -----------------------[ else ]----------------------> (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 (($token->type === Token::TYPE_KEYWORD)
&& ($token->flags & Token::FLAG_KEYWORD_RESERVED)
&& ((~$token->flags & Token::FLAG_KEYWORD_FUNCTION))
&& ($token->value !== 'DUAL')
&& ($token->value !== 'NULL')
&& ($token->value !== 'CASE')
) {
// No keyword is expected.
break;
}
if ($state === 0) {
if ($token->type === Token::TYPE_KEYWORD
&& $token->value === 'CASE'
) {
$expr = CaseExpression::parse($parser, $list, $options);
} else {
$expr = Expression::parse($parser, $list, $options);
}
if ($expr === null) {
break;
}
$ret[] = $expr;
$state = 1;
} elseif ($state === 1) {
if ($token->value === ',') {
$state = 0;
} else {
break;
}
}
}
if ($state === 0) {
$parser->error(
__('An expression was expected.'),
$list->tokens[$list->idx]
);
}
--$list->idx;
return $ret;
}
/**
* @param Expression[] $component The component to be built.
* @param array $options Parameters for building.
*
* @return string
*/
public static function build($component, array $options = array())
{
$ret = array();
foreach ($component as $frag) {
$ret[] = $frag::build($frag);
}
return implode($ret, ', ');
}
}

View File

@@ -0,0 +1,124 @@
<?php
/**
* Parses a function call.
*
* @package SqlParser
* @subpackage Components
*/
namespace SqlParser\Components;
use SqlParser\Component;
use SqlParser\Parser;
use SqlParser\Token;
use SqlParser\TokensList;
/**
* Parses a function call.
*
* @category Keywords
* @package SqlParser
* @subpackage Components
* @license https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
*/
class FunctionCall extends Component
{
/**
* The name of this function.
*
* @var string
*/
public $name;
/**
* The list of parameters
*
* @var ArrayObj
*/
public $parameters;
/**
* Constructor.
*
* @param string $name The name of the function to be called.
* @param array|ArrayObj $parameters The parameters of this function.
*/
public function __construct($name = null, $parameters = null)
{
$this->name = $name;
if (is_array($parameters)) {
$this->parameters = new ArrayObj($parameters);
} elseif ($parameters instanceof ArrayObj) {
$this->parameters = $parameters;
}
}
/**
* @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 FunctionCall
*/
public static function parse(Parser $parser, TokensList $list, array $options = array())
{
$ret = new FunctionCall();
/**
* The state of the parser.
*
* Below are the states of the parser.
*
* 0 ----------------------[ name ]-----------------------> 1
*
* 1 --------------------[ parameters ]-------------------> (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->name = $token->value;
$state = 1;
} elseif ($state === 1) {
if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '(')) {
$ret->parameters = ArrayObj::parse($parser, $list);
}
break;
}
}
return $ret;
}
/**
* @param FunctionCall $component The component to be built.
* @param array $options Parameters for building.
*
* @return string
*/
public static function build($component, array $options = array())
{
return $component->name . $component->parameters;
}
}

View File

@@ -0,0 +1,164 @@
<?php
/**
* `INTO` keyword parser.
*
* @package SqlParser
* @subpackage Components
*/
namespace SqlParser\Components;
use SqlParser\Component;
use SqlParser\Parser;
use SqlParser\Token;
use SqlParser\TokensList;
/**
* `INTO` keyword parser.
*
* @category Keywords
* @package SqlParser
* @subpackage Components
* @license https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
*/
class IntoKeyword extends Component
{
/**
* Type of target (OUTFILE or SYMBOL).
*
* @var string
*/
public $type;
/**
* The destination, which can be a table or a file.
*
* @var string|Expression
*/
public $dest;
/**
* The name of the columns.
*
* @var array
*/
public $columns;
/**
* The values to be selected into (SELECT .. INTO @var1)
*
* @var ExpressionArray
*/
public $values;
/**
* @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 IntoKeyword
*/
public static function parse(Parser $parser, TokensList $list, array $options = array())
{
$ret = new IntoKeyword();
/**
* The state of the parser.
*
* Below are the states of the parser.
*
* 0 -----------------------[ name ]----------------------> 1
* 0 ---------------------[ OUTFILE ]---------------------> 2
*
* 1 ------------------------[ ( ]------------------------> (END)
*
* 2 ---------------------[ filename ]--------------------> 1
*
* @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 (($token->type === Token::TYPE_KEYWORD) && ($token->flags & Token::FLAG_KEYWORD_RESERVED)) {
if (($state === 0) && ($token->value === 'OUTFILE')) {
$ret->type = 'OUTFILE';
$state = 2;
continue;
}
// No other keyword is expected.
break;
}
if ($state === 0) {
if ((isset($options['fromInsert'])
&& $options['fromInsert'])
|| (isset($options['fromReplace'])
&& $options['fromReplace'])
) {
$ret->dest = Expression::parse(
$parser,
$list,
array(
'parseField' => 'table',
'breakOnAlias' => true,
)
);
} else {
$ret->values = ExpressionArray::parse($parser, $list);
}
$state = 1;
} elseif ($state === 1) {
if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '(')) {
$ret->columns = ArrayObj::parse($parser, $list)->values;
++$list->idx;
}
break;
} elseif ($state === 2) {
$ret->dest = $token->value;
++$list->idx;
break;
}
}
--$list->idx;
return $ret;
}
/**
* @param IntoKeyword $component The component to be built.
* @param array $options Parameters for building.
*
* @return string
*/
public static function build($component, array $options = array())
{
if ($component->dest instanceof Expression) {
$columns = !empty($component->columns) ?
'(`' . implode('`, `', $component->columns) . '`)' : '';
return $component->dest . $columns;
} elseif (isset($component->values)) {
return ExpressionArray::build($component->values);
} else {
return 'OUTFILE "' . $component->dest . '"';
}
}
}

View File

@@ -0,0 +1,210 @@
<?php
/**
* `JOIN` keyword parser.
*
* @package SqlParser
* @subpackage Components
*/
namespace SqlParser\Components;
use SqlParser\Component;
use SqlParser\Parser;
use SqlParser\Token;
use SqlParser\TokensList;
/**
* `JOIN` keyword parser.
*
* @category Keywords
* @package SqlParser
* @subpackage Components
* @license https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
*/
class JoinKeyword extends Component
{
/**
* Types of join.
*
* @var array
*/
public static $JOINS = array(
'CROSS JOIN' => 'CROSS',
'FULL JOIN' => 'FULL',
'FULL OUTER JOIN' => 'FULL',
'INNER JOIN' => 'INNER',
'JOIN' => 'JOIN',
'LEFT JOIN' => 'LEFT',
'LEFT OUTER JOIN' => 'LEFT',
'RIGHT JOIN' => 'RIGHT',
'RIGHT OUTER JOIN' => 'RIGHT',
'NATURAL JOIN' => 'NATURAL',
'NATURAL LEFT JOIN' => 'NATURAL LEFT',
'NATURAL LEFT JOIN' => 'NATURAL LEFT',
'NATURAL RIGHT JOIN' => 'NATURAL RIGHT',
'NATURAL LEFT OUTER JOIN' => 'NATURAL LEFT OUTER',
'NATURAL RIGHT OUTER JOIN' => 'NATURAL RIGHT OUTER',
'STRAIGHT_JOIN' => 'STRAIGHT',
);
/**
* Type of this join.
*
* @see static::$JOINS
* @var string
*/
public $type;
/**
* Join expression.
*
* @var Expression
*/
public $expr;
/**
* Join conditions.
*
* @var Condition[]
*/
public $on;
/**
* Columns in Using clause
*
* @var ArrayObj
*/
public $using;
/**
* @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 JoinKeyword[]
*/
public static function parse(Parser $parser, TokensList $list, array $options = array())
{
$ret = array();
$expr = new JoinKeyword();
/**
* The state of the parser.
*
* Below are the states of the parser.
*
* 0 -----------------------[ JOIN ]----------------------> 1
*
* 1 -----------------------[ expr ]----------------------> 2
*
* 2 ------------------------[ ON ]-----------------------> 3
* 2 -----------------------[ USING ]---------------------> 4
*
* 3 --------------------[ conditions ]-------------------> 0
*
* 4 ----------------------[ columns ]--------------------> 0
*
* @var int $state
*/
$state = 0;
// By design, the parser will parse first token after the keyword.
// In this case, the keyword must be analyzed too, in order to determine
// the type of this join.
if ($list->idx > 0) {
--$list->idx;
}
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) {
if (($token->type === Token::TYPE_KEYWORD)
&& (!empty(static::$JOINS[$token->value]))
) {
$expr->type = static::$JOINS[$token->value];
$state = 1;
} else {
break;
}
} elseif ($state === 1) {
$expr->expr = Expression::parse($parser, $list, array('field' => 'table'));
$state = 2;
} elseif ($state === 2) {
if ($token->type === Token::TYPE_KEYWORD) {
if ($token->value === 'ON') {
$state = 3;
} elseif ($token->value === 'USING') {
$state = 4;
} else {
if (($token->type === Token::TYPE_KEYWORD)
&& (!empty(static::$JOINS[$token->value]))
) {
$ret[] = $expr;
$expr = new JoinKeyword();
$expr->type = static::$JOINS[$token->value];
$state = 1;
} else {
/* Next clause is starting */
break;
}
}
}
} elseif ($state === 3) {
$expr->on = Condition::parse($parser, $list);
$ret[] = $expr;
$expr = new JoinKeyword();
$state = 0;
} elseif ($state === 4) {
$expr->using = ArrayObj::parse($parser, $list);
$ret[] = $expr;
$expr = new JoinKeyword();
$state = 0;
}
}
if (!empty($expr->type)) {
$ret[] = $expr;
}
--$list->idx;
return $ret;
}
/**
* @param JoinKeyword[] $component The component to be built.
* @param array $options Parameters for building.
*
* @return string
*/
public static function build($component, array $options = array())
{
$ret = array();
foreach ($component as $c) {
$ret[] = array_search($c->type, static::$JOINS) . ' ' . $c->expr
. (!empty($c->on)
? ' ON ' . Condition::build($c->on) : '')
. (!empty($c->using)
? ' USING ' . ArrayObj::build($c->using) : '');
}
return implode(' ', $ret);
}
}

View File

@@ -0,0 +1,207 @@
<?php
/**
* Parses the definition of a key.
*
* @package SqlParser
* @subpackage Components
*/
namespace SqlParser\Components;
use SqlParser\Context;
use SqlParser\Component;
use SqlParser\Parser;
use SqlParser\Token;
use SqlParser\TokensList;
/**
* Parses the definition of a key.
*
* Used for parsing `CREATE TABLE` statement.
*
* @category Components
* @package SqlParser
* @subpackage Components
* @license https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
*/
class Key extends Component
{
/**
* All key options.
*
* @var array
*/
public static $KEY_OPTIONS = array(
'KEY_BLOCK_SIZE' => array(1, 'var'),
'USING' => array(2, 'var'),
'WITH PARSER' => array(3, 'var'),
'COMMENT' => array(4, 'var='),
);
/**
* The name of this key.
*
* @var string
*/
public $name;
/**
* Columns.
*
* @var array
*/
public $columns;
/**
* The type of this key.
*
* @var string
*/
public $type;
/**
* The options of this key.
*
* @var OptionsArray
*/
public $options;
/**
* Constructor.
*
* @param string $name The name of the key.
* @param array $columns The columns covered by this key.
* @param string $type The type of this key.
* @param OptionsArray $options The options of this key.
*/
public function __construct(
$name = null,
array $columns = array(),
$type = null,
$options = null
) {
$this->name = $name;
$this->columns = $columns;
$this->type = $type;
$this->options = $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 Key
*/
public static function parse(Parser $parser, TokensList $list, array $options = array())
{
$ret = new Key();
/**
* Last parsed column.
*
* @var array
*/
$lastColumn = array();
/**
* The state of the parser.
*
* Below are the states of the parser.
*
* 0 ----------------------[ type ]-----------------------> 1
*
* 1 ----------------------[ name ]-----------------------> 1
* 1 ---------------------[ columns ]---------------------> 2
*
* 2 ---------------------[ options ]---------------------> 3
*
* @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->type = $token->value;
$state = 1;
} elseif ($state === 1) {
if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '(')) {
$state = 2;
} else {
$ret->name = $token->value;
}
} elseif ($state === 2) {
if ($token->type === Token::TYPE_OPERATOR) {
if ($token->value === '(') {
$state = 3;
} elseif (($token->value === ',') || ($token->value === ')')) {
$state = ($token->value === ',') ? 2 : 4;
if (!empty($lastColumn)) {
$ret->columns[] = $lastColumn;
$lastColumn = array();
}
}
} else {
$lastColumn['name'] = $token->value;
}
} elseif ($state === 3) {
if (($token->type === Token::TYPE_OPERATOR) && ($token->value === ')')) {
$state = 2;
} else {
$lastColumn['length'] = $token->value;
}
} elseif ($state === 4) {
$ret->options = OptionsArray::parse($parser, $list, static::$KEY_OPTIONS);
++$list->idx;
break;
}
}
--$list->idx;
return $ret;
}
/**
* @param Key $component The component to be built.
* @param array $options Parameters for building.
*
* @return string
*/
public static function build($component, array $options = array())
{
$ret = $component->type . ' ';
if (!empty($component->name)) {
$ret .= Context::escape($component->name) . ' ';
}
$columns = array();
foreach ($component->columns as $column) {
$tmp = Context::escape($column['name']);
if (isset($column['length'])) {
$tmp .= '(' . $column['length'] . ')';
}
$columns[] = $tmp;
}
$ret .= '(' . implode(',', $columns) . ') ' . $component->options;
return trim($ret);
}
}

View File

@@ -0,0 +1,131 @@
<?php
/**
* `LIMIT` keyword parser.
*
* @package SqlParser
* @subpackage Components
*/
namespace SqlParser\Components;
use SqlParser\Component;
use SqlParser\Parser;
use SqlParser\Token;
use SqlParser\TokensList;
/**
* `LIMIT` keyword parser.
*
* @category Keywords
* @package SqlParser
* @subpackage Components
* @license https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
*/
class Limit extends Component
{
/**
* The number of rows skipped.
*
* @var int
*/
public $offset;
/**
* The number of rows to be returned.
*
* @var int
*/
public $rowCount;
/**
* Constructor.
*
* @param int $rowCount The row count.
* @param int $offset The offset.
*/
public function __construct($rowCount = 0, $offset = 0)
{
$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.
* @param array $options Parameters for parsing.
*
* @return Limit
*/
public static function parse(Parser $parser, TokensList $list, array $options = array())
{
$ret = new Limit();
$offset = false;
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 (($token->type === Token::TYPE_KEYWORD) && ($token->flags & Token::FLAG_KEYWORD_RESERVED)) {
break;
}
if (($token->type === Token::TYPE_KEYWORD) && ($token->value === 'OFFSET')) {
if ($offset) {
$parser->error(__('An offset was expected.'), $token);
}
$offset = true;
continue;
}
if (($token->type === Token::TYPE_OPERATOR) && ($token->value === ',')) {
$ret->offset = $ret->rowCount;
$ret->rowCount = 0;
continue;
}
if ($offset) {
$ret->offset = $token->value;
$offset = false;
} else {
$ret->rowCount = $token->value;
}
}
if ($offset) {
$parser->error(
__('An offset was expected.'),
$list->tokens[$list->idx - 1]
);
}
--$list->idx;
return $ret;
}
/**
* @param Limit $component The component to be built.
* @param array $options Parameters for building.
*
* @return string
*/
public static function build($component, array $options = array())
{
return $component->offset . ', ' . $component->rowCount;
}
}

View File

@@ -0,0 +1,372 @@
<?php
/**
* Parses a list of options.
*
* @package SqlParser
* @subpackage Components
*/
namespace SqlParser\Components;
use SqlParser\Component;
use SqlParser\Parser;
use SqlParser\Token;
use SqlParser\TokensList;
/**
* Parses a list of options.
*
* @category Components
* @package SqlParser
* @subpackage Components
* @license https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
*/
class OptionsArray extends Component
{
/**
* ArrayObj of selected options.
*
* @var array
*/
public $options = array();
/**
* Constructor.
*
* @param array $options The array of options. Options that have a value
* must be an array with at least two keys `name` and
* `expr` or `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.
* @param array $options Parameters for parsing.
*
* @return OptionsArray
*/
public static function parse(Parser $parser, TokensList $list, array $options = array())
{
$ret = new OptionsArray();
/**
* 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;
/**
* The state of the parser.
*
* Below are the states of the parser.
*
* 0 ---------------------[ option ]----------------------> 1
*
* 1 -------------------[ = (optional) ]------------------> 2
*
* 2 ----------------------[ value ]----------------------> 0
*
* @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 comments.
if ($token->type === Token::TYPE_COMMENT) {
continue;
}
// Skipping whitespace if not parsing value.
if (($token->type === Token::TYPE_WHITESPACE) && ($brackets === 0)) {
continue;
}
if ($lastOption === null) {
$upper = strtoupper($token->token);
if (isset($options[$upper])) {
$lastOption = $options[$upper];
$lastOptionId = is_array($lastOption) ?
$lastOption[0] : $lastOption;
$state = 0;
// Checking for option conflicts.
// For example, in `SELECT` statements the keywords `ALL`
// and `DISTINCT` conflict and if used together, they
// produce an invalid query.
//
// Usually, tokens can be identified in the array by the
// option ID, but if conflicts occur, a generated option ID
// is used.
//
// The first pseudo duplicate ID is the maximum value of the
// real options (e.g. if there are 5 options, the first
// fake ID is 6).
if (isset($ret->options[$lastOptionId])) {
$parser->error(
sprintf(
__('This option conflicts with "%1$s".'),
is_array($ret->options[$lastOptionId])
? $ret->options[$lastOptionId]['name']
: $ret->options[$lastOptionId]
),
$token
);
$lastOptionId = $lastAssignedId++;
}
} else {
// There is no option to be processed.
break;
}
}
if ($state === 0) {
if (!is_array($lastOption)) {
// This is a just keyword option without any value.
// This is the beginning and the end of it.
$ret->options[$lastOptionId] = $token->value;
$lastOption = null;
$state = 0;
} elseif (($lastOption[1] === 'var') || ($lastOption[1] === 'var=')) {
// This is a keyword that is followed by a value.
// This is only the beginning. The value is parsed in state
// 1 and 2. State 1 is used to skip the first equals sign
// and state 2 to parse the actual value.
$ret->options[$lastOptionId] = array(
// @var string The name of the option.
'name' => $token->value,
// @var bool Whether it contains an equal sign.
// This is used by the builder to rebuild it.
'equals' => $lastOption[1] === 'var=',
// @var string Raw value.
'expr' => '',
// @var string Processed value.
'value' => '',
);
$state = 1;
} elseif ($lastOption[1] === 'expr' || $lastOption[1] === 'expr=') {
// This is a keyword that is followed by an expression.
// The expression is used by the specialized parser.
// Skipping this option in order to parse the expression.
++$list->idx;
$ret->options[$lastOptionId] = array(
// @var string The name of the option.
'name' => $token->value,
// @var bool Whether it contains an equal sign.
// This is used by the builder to rebuild it.
'equals' => $lastOption[1] === 'expr=',
// @var Expression The parsed expression.
'expr' => '',
);
$state = 1;
}
} elseif ($state === 1) {
$state = 2;
if ($token->token === '=') {
$ret->options[$lastOptionId]['equals'] = true;
continue;
}
}
// This is outside the `elseif` group above because the change might
// change this iteration.
if ($state === 2) {
if ($lastOption[1] === 'expr' || $lastOption[1] === 'expr=') {
$ret->options[$lastOptionId]['expr'] = Expression::parse(
$parser,
$list,
empty($lastOption[2]) ? array() : $lastOption[2]
);
$ret->options[$lastOptionId]['value']
= $ret->options[$lastOptionId]['expr']->expr;
$lastOption = null;
$state = 0;
} else {
if ($token->token === '(') {
++$brackets;
} elseif ($token->token === ')') {
--$brackets;
}
$ret->options[$lastOptionId]['expr'] .= $token->token;
if (!((($token->token === '(') && ($brackets === 1))
|| (($token->token === ')') && ($brackets === 0)))
) {
// First pair of brackets is being skipped.
$ret->options[$lastOptionId]['value'] .= $token->value;
}
// Checking if we finished parsing.
if ($brackets === 0) {
$lastOption = null;
}
}
}
}
/*
* We reached the end of statement without getting a value
* for an option for which a value was required
*/
if ($state === 1
&& $lastOption
&& ($lastOption[1] == 'expr'
|| $lastOption[1] == 'var'
|| $lastOption[1] == 'var='
|| $lastOption[1] == 'expr=')
) {
$parser->error(
sprintf(
__('Value/Expression for the option %1$s was expected'),
$ret->options[$lastOptionId]['name']
),
$list->tokens[$list->idx - 1]
);
}
if (empty($options['_UNSORTED'])) {
ksort($ret->options);
}
--$list->idx;
return $ret;
}
/**
* @param OptionsArray $component The component to be built.
* @param array $options Parameters for building.
*
* @return string
*/
public static function build($component, array $options = array())
{
if (empty($component->options)) {
return '';
}
$options = array();
foreach ($component->options as $option) {
if (!is_array($option)) {
$options[] = $option;
} else {
$options[] = $option['name']
. ((!empty($option['equals']) && $option['equals']) ? '=' : ' ')
. (!empty($option['expr']) ? $option['expr'] : $option['value']);
}
}
return implode(' ', $options);
}
/**
* Checks if it has the specified option and returns it value or true.
*
* @param string $key The key to be checked.
* @param bool $getExpr Gets the expression instead of the value.
* The value is the processed form of the expression.
*
* @return mixed
*/
public function has($key, $getExpr = false)
{
foreach ($this->options as $option) {
if (is_array($option)) {
if (!strcasecmp($key, $option['name'])) {
return $getExpr ? $option['expr'] : $option['value'];
}
} elseif (!strcasecmp($key, $option)) {
return true;
}
}
return false;
}
/**
* Removes the option from the array.
*
* @param string $key The key to be removed.
*
* @return bool Whether the key was found and deleted or not.
*/
public function remove($key)
{
foreach ($this->options as $idx => $option) {
if (is_array($option)) {
if (!strcasecmp($key, $option['name'])) {
unset($this->options[$idx]);
return true;
}
} elseif (!strcasecmp($key, $option)) {
unset($this->options[$idx]);
return true;
}
}
return false;
}
/**
* Merges the specified options with these ones. Values with same ID will be
* replaced.
*
* @param array|OptionsArray $options The options to be merged.
*
* @return void
*/
public function merge($options)
{
if (is_array($options)) {
$this->options = array_merge_recursive($this->options, $options);
} elseif ($options instanceof OptionsArray) {
$this->options = array_merge_recursive($this->options, $options->options);
}
}
/**
* Checks tf there are no options set.
*
* @return bool
*/
public function isEmpty()
{
return empty($this->options);
}
}

View File

@@ -0,0 +1,144 @@
<?php
/**
* `ORDER BY` keyword parser.
*
* @package SqlParser
* @subpackage Components
*/
namespace SqlParser\Components;
use SqlParser\Component;
use SqlParser\Parser;
use SqlParser\Token;
use SqlParser\TokensList;
/**
* `ORDER BY` keyword parser.
*
* @category Keywords
* @package SqlParser
* @subpackage Components
* @license https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
*/
class OrderKeyword extends Component
{
/**
* The expression that is used for ordering.
*
* @var Expression
*/
public $expr;
/**
* The order type.
*
* @var string
*/
public $type;
/**
* Constructor.
*
* @param Expression $expr The expression that we are sorting by.
* @param string $type The sorting type.
*/
public function __construct($expr = null, $type = 'ASC')
{
$this->expr = $expr;
$this->type = $type;
}
/**
* @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 OrderKeyword[]
*/
public static function parse(Parser $parser, TokensList $list, array $options = array())
{
$ret = array();
$expr = new OrderKeyword();
/**
* The state of the parser.
*
* Below are the states of the parser.
*
* 0 --------------------[ expression ]-------------------> 1
*
* 1 ------------------------[ , ]------------------------> 0
* 1 -------------------[ ASC / DESC ]--------------------> 1
*
* @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) {
$expr->expr = Expression::parse($parser, $list);
$state = 1;
} elseif ($state === 1) {
if (($token->type === Token::TYPE_KEYWORD)
&& (($token->value === 'ASC') || ($token->value === 'DESC'))
) {
$expr->type = $token->value;
} elseif (($token->type === Token::TYPE_OPERATOR)
&& ($token->value === ',')
) {
if (!empty($expr->expr)) {
$ret[] = $expr;
}
$expr = new OrderKeyword();
$state = 0;
} else {
break;
}
}
}
// Last iteration was not processed.
if (!empty($expr->expr)) {
$ret[] = $expr;
}
--$list->idx;
return $ret;
}
/**
* @param OrderKeyword|OrderKeyword[] $component The component to be built.
* @param array $options Parameters for building.
*
* @return string
*/
public static function build($component, array $options = array())
{
if (is_array($component)) {
return implode(', ', $component);
} else {
return $component->expr . ' ' . $component->type;
}
}
}

View File

@@ -0,0 +1,160 @@
<?php
/**
* The definition of a parameter of a function or procedure.
*
* @package SqlParser
* @subpackage Components
*/
namespace SqlParser\Components;
use SqlParser\Context;
use SqlParser\Component;
use SqlParser\Parser;
use SqlParser\Token;
use SqlParser\TokensList;
/**
* The definition of a parameter of a function or procedure.
*
* @category Components
* @package SqlParser
* @subpackage Components
* @license https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
*/
class ParameterDefinition extends Component
{
/**
* The name of the new column.
*
* @var string
*/
public $name;
/**
* Parameter's direction (IN, OUT or INOUT).
*
* @var string
*/
public $inOut;
/**
* The data type of thew new column.
*
* @var DataType
*/
public $type;
/**
* @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 ParameterDefinition[]
*/
public static function parse(Parser $parser, TokensList $list, array $options = array())
{
$ret = array();
$expr = new ParameterDefinition();
/**
* The state of the parser.
*
* Below are the states of the parser.
*
* 0 -----------------------[ ( ]------------------------> 1
*
* 1 ----------------[ IN / OUT / INOUT ]----------------> 1
* 1 ----------------------[ name ]----------------------> 2
*
* 2 -------------------[ data type ]--------------------> 3
*
* 3 ------------------------[ , ]-----------------------> 1
* 3 ------------------------[ ) ]-----------------------> (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) {
if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '(')) {
$state = 1;
}
continue;
} elseif ($state === 1) {
if (($token->value === 'IN') || ($token->value === 'OUT') || ($token->value === 'INOUT')) {
$expr->inOut = $token->value;
++$list->idx;
} elseif ($token->value === ')') {
++$list->idx;
break;
} else {
$expr->name = $token->value;
$state = 2;
}
} elseif ($state === 2) {
$expr->type = DataType::parse($parser, $list);
$state = 3;
} elseif ($state === 3) {
$ret[] = $expr;
$expr = new ParameterDefinition();
if ($token->value === ',') {
$state = 1;
} elseif ($token->value === ')') {
++$list->idx;
break;
}
}
}
// Last iteration was not saved.
if ((isset($expr->name)) && ($expr->name !== '')) {
$ret[] = $expr;
}
--$list->idx;
return $ret;
}
/**
* @param ParameterDefinition[] $component The component to be built.
* @param array $options Parameters for building.
*
* @return string
*/
public static function build($component, array $options = array())
{
if (is_array($component)) {
return '(' . implode(', ', $component) . ')';
} else {
$tmp = '';
if (!empty($component->inOut)) {
$tmp .= $component->inOut . ' ';
}
return trim(
$tmp . Context::escape($component->name) . ' ' . $component->type
);
}
}
}

View File

@@ -0,0 +1,223 @@
<?php
/**
* Parses the create definition of a partition.
*
* Used for parsing `CREATE TABLE` statement.
*
* @package SqlParser
* @subpackage Components
*/
namespace SqlParser\Components;
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
* @license https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
*/
class PartitionDefinition extends Component
{
/**
* All field options.
*
* @var array
*/
public static $OPTIONS = array(
'STORAGE ENGINE' => array(1, 'var'),
'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|string
*/
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;
// Looking ahead for a 'VALUES' keyword.
$idx = $list->idx;
$list->getNext();
$nextToken = $list->getNext();
$list->idx = $idx;
$state = ($nextToken->type === Token::TYPE_KEYWORD)
&& ($nextToken->value === 'VALUES')
? 2 : 5;
} 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(
'parenthesesDelimited' => true,
'breakOnAlias' => 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.
* @param array $options Parameters for building.
*
* @return string
*/
public static function build($component, array $options = array())
{
if (is_array($component)) {
return "(\n" . implode(",\n", $component) . "\n)";
} else {
if ($component->isSubpartition) {
return trim('SUBPARTITION ' . $component->name . ' ' . $component->options);
} else {
$subpartitions = empty($component->subpartitions)
? '' : ' ' . PartitionDefinition::build($component->subpartitions);
return trim(
'PARTITION ' . $component->name
. (empty($component->type) ? '' : ' VALUES ' . $component->type . ' ' . $component->expr . ' ')
. $component->options . $subpartitions
);
}
}
}
}

View File

@@ -0,0 +1,157 @@
<?php
/**
* `REFERENCES` keyword parser.
*
* @package SqlParser
* @subpackage Components
*/
namespace SqlParser\Components;
use SqlParser\Context;
use SqlParser\Component;
use SqlParser\Parser;
use SqlParser\Token;
use SqlParser\TokensList;
/**
* `REFERENCES` keyword parser.
*
* @category Keywords
* @package SqlParser
* @subpackage Components
* @license https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
*/
class Reference extends Component
{
/**
* All references options.
*
* @var array
*/
public static $REFERENCES_OPTIONS = array(
'MATCH' => array(1, 'var'),
'ON DELETE' => array(2, 'var'),
'ON UPDATE' => array(3, 'var'),
);
/**
* The referenced table.
*
* @var Expression
*/
public $table;
/**
* The referenced columns.
*
* @var array
*/
public $columns;
/**
* The options of the referencing.
*
* @var OptionsArray
*/
public $options;
/**
* Constructor.
*
* @param Expression $table The name of the table referenced.
* @param array $columns The columns referenced.
* @param OptionsArray $options The options.
*/
public function __construct($table = null, array $columns = array(), $options = null)
{
$this->table = $table;
$this->columns = $columns;
$this->options = $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 Reference
*/
public static function parse(Parser $parser, TokensList $list, array $options = array())
{
$ret = new Reference();
/**
* The state of the parser.
*
* Below are the states of the parser.
*
* 0 ----------------------[ table ]---------------------> 1
*
* 1 ---------------------[ columns ]--------------------> 2
*
* 2 ---------------------[ options ]--------------------> (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->table = Expression::parse(
$parser,
$list,
array(
'parseField' => 'table',
'breakOnAlias' => true,
)
);
$state = 1;
} elseif ($state === 1) {
$ret->columns = ArrayObj::parse($parser, $list)->values;
$state = 2;
} elseif ($state === 2) {
$ret->options = OptionsArray::parse($parser, $list, static::$REFERENCES_OPTIONS);
++$list->idx;
break;
}
}
--$list->idx;
return $ret;
}
/**
* @param Reference $component The component to be built.
* @param array $options Parameters for building.
*
* @return string
*/
public static function build($component, array $options = array())
{
return trim(
$component->table
. ' (' . implode(', ', Context::escape($component->columns)) . ') '
. $component->options
);
}
}

View File

@@ -0,0 +1,173 @@
<?php
/**
* `RENAME TABLE` keyword parser.
*
* @package SqlParser
* @subpackage Components
*/
namespace SqlParser\Components;
use SqlParser\Component;
use SqlParser\Parser;
use SqlParser\Token;
use SqlParser\TokensList;
/**
* `RENAME TABLE` keyword parser.
*
* @category Keywords
* @package SqlParser
* @subpackage Components
* @license https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
*/
class RenameOperation extends Component
{
/**
* The old table name.
*
* @var Expression
*/
public $old;
/**
* The new table name.
*
* @var Expression
*/
public $new;
/**
* @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 RenameOperation[]
*/
public static function parse(Parser $parser, TokensList $list, array $options = array())
{
$ret = array();
$expr = new RenameOperation();
/**
* The state of the parser.
*
* Below are the states of the parser.
*
* 0 ---------------------[ old name ]--------------------> 1
*
* 1 ------------------------[ TO ]-----------------------> 2
*
* 2 ---------------------[ old name ]--------------------> 3
*
* 3 ------------------------[ , ]------------------------> 0
* 3 -----------------------[ else ]----------------------> (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) {
$expr->old = Expression::parse(
$parser,
$list,
array(
'breakOnAlias' => true,
'parseField' => 'table',
)
);
if (empty($expr->old)) {
$parser->error(
__('The old name of the table was expected.'),
$token
);
}
$state = 1;
} elseif ($state === 1) {
if (($token->type === Token::TYPE_KEYWORD) && ($token->value === 'TO')) {
$state = 2;
} else {
$parser->error(
__('Keyword "TO" was expected.'),
$token
);
break;
}
} elseif ($state === 2) {
$expr->new = Expression::parse(
$parser,
$list,
array(
'breakOnAlias' => true,
'parseField' => 'table',
)
);
if (empty($expr->new)) {
$parser->error(
__('The new name of the table was expected.'),
$token
);
}
$state = 3;
} elseif ($state === 3) {
if (($token->type === Token::TYPE_OPERATOR) && ($token->value === ',')) {
$ret[] = $expr;
$expr = new RenameOperation();
$state = 0;
} else {
break;
}
}
}
if ($state !== 3) {
$parser->error(
__('A rename operation was expected.'),
$list->tokens[$list->idx - 1]
);
}
// Last iteration was not saved.
if (!empty($expr->old)) {
$ret[] = $expr;
}
--$list->idx;
return $ret;
}
/**
* @param RenameOperation $component The component to be built.
* @param array $options Parameters for building.
*
* @return string
*/
public static function build($component, array $options = array())
{
if (is_array($component)) {
return implode(', ', $component);
} else {
return $component->old . ' TO ' . $component->new;
}
}
}

View File

@@ -0,0 +1,137 @@
<?php
/**
* `SET` keyword parser.
*
* @package SqlParser
* @subpackage Components
*/
namespace SqlParser\Components;
use SqlParser\Component;
use SqlParser\Parser;
use SqlParser\Token;
use SqlParser\TokensList;
/**
* `SET` keyword parser.
*
* @category Keywords
* @package SqlParser
* @subpackage Components
* @license https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
*/
class SetOperation extends Component
{
/**
* The name of the column that is being updated.
*
* @var string
*/
public $column;
/**
* The new value.
*
* @var string
*/
public $value;
/**
* @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 SetOperation[]
*/
public static function parse(Parser $parser, TokensList $list, array $options = array())
{
$ret = array();
$expr = new SetOperation();
/**
* The state of the parser.
*
* Below are the states of the parser.
*
* 0 -------------------[ column name ]-------------------> 1
*
* 1 ------------------------[ , ]------------------------> 0
* 1 ----------------------[ value ]----------------------> 1
*
* @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;
}
// No keyword is expected.
if (($token->type === Token::TYPE_KEYWORD)
&& ($token->flags & Token::FLAG_KEYWORD_RESERVED)
&& ($state == 0)
) {
break;
}
if ($state === 0) {
if ($token->token === '=') {
$state = 1;
} elseif ($token->value !== ',') {
$expr->column .= $token->token;
}
} elseif ($state === 1) {
$tmp = Expression::parse(
$parser,
$list,
array(
'breakOnAlias' => true,
)
);
if ($tmp == null) {
break;
}
$expr->column = trim($expr->column);
$expr->value = $tmp->expr;
$ret[] = $expr;
$expr = new SetOperation();
$state = 0;
}
}
--$list->idx;
return $ret;
}
/**
* @param SetOperation|SetOperation[] $component The component to be built.
* @param array $options Parameters for building.
*
* @return string
*/
public static function build($component, array $options = array())
{
if (is_array($component)) {
return implode(', ', $component);
} else {
return $component->column . ' = ' . $component->value;
}
}
}

View File

@@ -0,0 +1,39 @@
<?php
/**
* `UNION` keyword builder.
*
* @package SqlParser
* @subpackage Components
*/
namespace SqlParser\Components;
use SqlParser\Component;
use SqlParser\Statements\SelectStatement;
/**
* `UNION` keyword builder.
*
* @category Keywords
* @package SqlParser
* @subpackage Components
* @license https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
*/
class UnionKeyword extends Component
{
/**
* @param SelectStatement[] $component The component to be built.
* @param array $options Parameters for building.
*
* @return string
*/
public static function build($component, array $options = array())
{
$tmp = array();
foreach ($component as $component) {
$tmp[] = $component[0] . ' ' . $component[1];
}
return implode(' ', $tmp);
}
}