Fixes #11956. unrecognized keyword interval Fixes issues regarding field names and aliases. Signed-off-by: Dan Ungureanu <udan1107@gmail.com>
564 lines
19 KiB
PHP
564 lines
19 KiB
PHP
<?php
|
|
|
|
/**
|
|
* `CREATE` statement.
|
|
*
|
|
* @package SqlParser
|
|
* @subpackage Statements
|
|
*/
|
|
namespace SqlParser\Statements;
|
|
|
|
use SqlParser\Parser;
|
|
use SqlParser\Statement;
|
|
use SqlParser\Token;
|
|
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;
|
|
|
|
/**
|
|
* `CREATE` statement.
|
|
*
|
|
* @category Statements
|
|
* @package SqlParser
|
|
* @subpackage Statements
|
|
* @author Dan Ungureanu <udan1107@gmail.com>
|
|
* @license http://opensource.org/licenses/GPL-2.0 GNU Public License
|
|
*/
|
|
class CreateStatement extends Statement
|
|
{
|
|
|
|
/**
|
|
* Options for `CREATE` statements.
|
|
*
|
|
* @var array
|
|
*/
|
|
public static $OPTIONS = array(
|
|
|
|
// CREATE TABLE
|
|
'TEMPORARY' => 1,
|
|
|
|
// CREATE VIEW
|
|
'OR REPLACE' => array(2, 'var='),
|
|
'ALGORITHM' => array(3, 'var='),
|
|
// `DEFINER` is also used for `CREATE FUNCTION / PROCEDURE`
|
|
'DEFINER' => array(4, 'var='),
|
|
'SQL SECURITY' => array(5, 'var'),
|
|
|
|
'DATABASE' => 6,
|
|
'EVENT' => 6,
|
|
'FUNCTION' => 6,
|
|
'INDEX' => 6,
|
|
'PROCEDURE' => 6,
|
|
'SERVER' => 6,
|
|
'TABLE' => 6,
|
|
'TABLESPACE' => 6,
|
|
'TRIGGER' => 6,
|
|
'USER' => 6,
|
|
'VIEW' => 6,
|
|
|
|
// CREATE TABLE
|
|
'IF NOT EXISTS' => 7,
|
|
);
|
|
|
|
/**
|
|
* 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='),
|
|
'DEFAULT COLLATE' => array(2, 'var='),
|
|
'COLLATE' => array(2, 'var='),
|
|
);
|
|
|
|
/**
|
|
* All table options.
|
|
*
|
|
* @var array
|
|
*/
|
|
public static $TABLE_OPTIONS = array(
|
|
'ENGINE' => array(1, 'var='),
|
|
'AUTO_INCREMENT' => array(2, 'var='),
|
|
'AVG_ROW_LENGTH' => array(3, 'var'),
|
|
'CHARACTER SET' => array(4, 'var='),
|
|
'CHARSET' => array(4, 'var='),
|
|
'DEFAULT CHARACTER SET' => array(4, 'var='),
|
|
'DEFAULT CHARSET' => array(4, 'var='),
|
|
'CHECKSUM' => array(5, 'var'),
|
|
'DEFAULT COLLATE' => array(6, 'var='),
|
|
'COLLATE' => array(6, 'var='),
|
|
'COMMENT' => array(7, 'var='),
|
|
'CONNECTION' => array(8, 'var'),
|
|
'DATA DIRECTORY' => array(9, 'var'),
|
|
'DELAY_KEY_WRITE' => array(10, 'var'),
|
|
'INDEX DIRECTORY' => array(11, 'var'),
|
|
'INSERT_METHOD' => array(12, 'var'),
|
|
'KEY_BLOCK_SIZE' => array(13, 'var'),
|
|
'MAX_ROWS' => array(14, 'var'),
|
|
'MIN_ROWS' => array(15, 'var'),
|
|
'PACK_KEYS' => array(16, 'var'),
|
|
'PASSWORD' => array(17, 'var'),
|
|
'ROW_FORMAT' => array(18, 'var'),
|
|
'TABLESPACE' => array(19, 'var'),
|
|
'STORAGE' => array(20, 'var'),
|
|
'UNION' => array(21, 'var'),
|
|
);
|
|
|
|
/**
|
|
* All function options.
|
|
*
|
|
* @var array
|
|
*/
|
|
public static $FUNC_OPTIONS = array(
|
|
'COMMENT' => array(1, 'var='),
|
|
'LANGUAGE SQL' => 2,
|
|
'DETERMINISTIC' => 3,
|
|
'NOT DETERMINISTIC' => 3,
|
|
'CONTAINS SQL' => 4,
|
|
'NO SQL' => 4,
|
|
'READS SQL DATA' => 4,
|
|
'MODIFIES SQL DATA' => 4,
|
|
'SQL SECURITY DEFINER' => array(5, 'var'),
|
|
);
|
|
|
|
/**
|
|
* All trigger options.
|
|
*
|
|
* @var array
|
|
*/
|
|
public static $TRIGGER_OPTIONS = array(
|
|
'BEFORE' => 1,
|
|
'AFTER' => 1,
|
|
'INSERT' => 2,
|
|
'UPDATE' => 2,
|
|
'DELETE' => 2,
|
|
);
|
|
|
|
/**
|
|
* The name of the entity that is created.
|
|
*
|
|
* Used by all `CREATE` statements.
|
|
*
|
|
* @var Expression
|
|
*/
|
|
public $name;
|
|
|
|
/**
|
|
* The options of the entity (table, procedure, function, etc.).
|
|
*
|
|
* Used by `CREATE TABLE`, `CREATE FUNCTION` and `CREATE PROCEDURE`.
|
|
*
|
|
* @var OptionsArray
|
|
*
|
|
* @see static::$TABLE_OPTIONS
|
|
* @see static::$FUNC_OPTIONS
|
|
* @see static::$TRIGGER_OPTIONS
|
|
*/
|
|
public $entityOptions;
|
|
|
|
/**
|
|
* If `CREATE TABLE`, a list of columns and keys.
|
|
* If `CREATE VIEW`, a list of columns.
|
|
*
|
|
* Used by `CREATE TABLE` and `CREATE VIEW`.
|
|
*
|
|
* @var CreateDefinition[]|ArrayObj
|
|
*/
|
|
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.
|
|
*
|
|
* Used by `CREATE TRIGGER`.
|
|
*
|
|
* @var Expression
|
|
*/
|
|
public $table;
|
|
|
|
/**
|
|
* The return data type of this routine.
|
|
*
|
|
* Used by `CREATE FUNCTION`.
|
|
*
|
|
* @var DataType
|
|
*/
|
|
public $return;
|
|
|
|
/**
|
|
* The parameters of this routine.
|
|
*
|
|
* Used by `CREATE FUNCTION` and `CREATE PROCEDURE`.
|
|
*
|
|
* @var ParameterDefinition[]
|
|
*/
|
|
public $parameters;
|
|
|
|
/**
|
|
* The body of this function or procedure. For views, it is the select
|
|
* statement that gets the
|
|
*
|
|
* Used by `CREATE FUNCTION`, `CREATE PROCEDURE` and `CREATE VIEW`.
|
|
*
|
|
* @var Token[]|string
|
|
*/
|
|
public $body = array();
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function build()
|
|
{
|
|
$fields = '';
|
|
if (!empty($this->fields)) {
|
|
if (is_array($this->fields)) {
|
|
$fields = CreateDefinition::build($this->fields) . ' ';
|
|
} elseif ($this->fields instanceof ArrayObj) {
|
|
$fields = ArrayObj::build($this->fields);
|
|
}
|
|
}
|
|
if ($this->options->has('DATABASE')) {
|
|
return 'CREATE '
|
|
. OptionsArray::build($this->options) . ' '
|
|
. 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)
|
|
. $partition;
|
|
} elseif ($this->options->has('VIEW')) {
|
|
return 'CREATE '
|
|
. OptionsArray::build($this->options) . ' '
|
|
. Expression::build($this->name) . ' '
|
|
. $fields . ' AS ' . TokensList::build($this->body) . ' '
|
|
. OptionsArray::build($this->entityOptions);
|
|
} elseif ($this->options->has('TRIGGER')) {
|
|
return 'CREATE '
|
|
. OptionsArray::build($this->options) . ' '
|
|
. Expression::build($this->name) . ' '
|
|
. OptionsArray::build($this->entityOptions) . ' '
|
|
. 'ON ' . Expression::build($this->table) . ' '
|
|
. 'FOR EACH ROW ' . TokensList::build($this->body);
|
|
} elseif (($this->options->has('PROCEDURE'))
|
|
|| ($this->options->has('FUNCTION'))
|
|
) {
|
|
$tmp = '';
|
|
if ($this->options->has('FUNCTION')) {
|
|
$tmp = 'RETURNS ' . DataType::build($this->return);
|
|
}
|
|
return 'CREATE '
|
|
. OptionsArray::build($this->options) . ' '
|
|
. Expression::build($this->name) . ' '
|
|
. ParameterDefinition::build($this->parameters) . ' '
|
|
. $tmp . ' ' . TokensList::build($this->body);
|
|
} else {
|
|
return 'CREATE '
|
|
. OptionsArray::build($this->options) . ' '
|
|
. Expression::build($this->name) . ' '
|
|
. TokensList::build($this->body);
|
|
}
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* @param Parser $parser The instance that requests parsing.
|
|
* @param TokensList $list The list of tokens to be parsed.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function parse(Parser $parser, TokensList $list)
|
|
{
|
|
++$list->idx; // Skipping `CREATE`.
|
|
|
|
// Parsing options.
|
|
$this->options = OptionsArray::parse($parser, $list, static::$OPTIONS);
|
|
++$list->idx; // Skipping last option.
|
|
|
|
// Parsing the field name.
|
|
$this->name = Expression::parse(
|
|
$parser,
|
|
$list,
|
|
array(
|
|
'parseField' => 'table',
|
|
'breakOnAlias' => true,
|
|
)
|
|
);
|
|
|
|
if ((!isset($this->name)) || ($this->name === '')) {
|
|
$parser->error(
|
|
__('The name of the entity was expected.'),
|
|
$list->tokens[$list->idx]
|
|
);
|
|
} else {
|
|
++$list->idx; // Skipping field.
|
|
}
|
|
|
|
if ($this->options->has('DATABASE')) {
|
|
$this->entityOptions = OptionsArray::parse(
|
|
$parser,
|
|
$list,
|
|
static::$DB_OPTIONS
|
|
);
|
|
} elseif ($this->options->has('TABLE')) {
|
|
$this->fields = CreateDefinition::parse($parser, $list);
|
|
if (empty($this->fields)) {
|
|
$parser->error(
|
|
__('At least one column definition was expected.'),
|
|
$list->tokens[$list->idx]
|
|
);
|
|
}
|
|
++$list->idx;
|
|
|
|
$this->entityOptions = OptionsArray::parse(
|
|
$parser,
|
|
$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'))
|
|
) {
|
|
$this->parameters = ParameterDefinition::parse($parser, $list);
|
|
if ($this->options->has('FUNCTION')) {
|
|
$token = $list->getNextOfType(Token::TYPE_KEYWORD);
|
|
if ($token->value !== 'RETURNS') {
|
|
$parser->error(
|
|
__('A "RETURNS" keyword was expected.'),
|
|
$token
|
|
);
|
|
} else {
|
|
++$list->idx;
|
|
$this->return = DataType::parse(
|
|
$parser,
|
|
$list
|
|
);
|
|
}
|
|
}
|
|
++$list->idx;
|
|
|
|
$this->entityOptions = OptionsArray::parse(
|
|
$parser,
|
|
$list,
|
|
static::$FUNC_OPTIONS
|
|
);
|
|
++$list->idx;
|
|
|
|
for (; $list->idx < $list->count; ++$list->idx) {
|
|
$token = $list->tokens[$list->idx];
|
|
$this->body[] = $token;
|
|
}
|
|
} elseif ($this->options->has('VIEW')) {
|
|
$token = $list->getNext(); // Skipping whitespaces and comments.
|
|
|
|
// Parsing columns list.
|
|
if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '(')) {
|
|
--$list->idx; // getNext() also goes forward one field.
|
|
$this->fields = ArrayObj::parse($parser, $list);
|
|
++$list->idx; // Skipping last token from the array.
|
|
$list->getNext();
|
|
}
|
|
|
|
// Parsing the `AS` keyword.
|
|
for (; $list->idx < $list->count; ++$list->idx) {
|
|
$token = $list->tokens[$list->idx];
|
|
if ($token->type === Token::TYPE_DELIMITER) {
|
|
break;
|
|
}
|
|
$this->body[] = $token;
|
|
}
|
|
} elseif ($this->options->has('TRIGGER')) {
|
|
// Parsing the time and the event.
|
|
$this->entityOptions = OptionsArray::parse(
|
|
$parser,
|
|
$list,
|
|
static::$TRIGGER_OPTIONS
|
|
);
|
|
++$list->idx;
|
|
|
|
$list->getNextOfTypeAndValue(Token::TYPE_KEYWORD, 'ON');
|
|
++$list->idx; // Skipping `ON`.
|
|
|
|
// Parsing the name of the table.
|
|
$this->table = Expression::parse(
|
|
$parser,
|
|
$list,
|
|
array(
|
|
'parseField' => 'table',
|
|
'breakOnAlias' => true,
|
|
)
|
|
);
|
|
++$list->idx;
|
|
|
|
$list->getNextOfTypeAndValue(Token::TYPE_KEYWORD, 'FOR EACH ROW');
|
|
++$list->idx; // Skipping `FOR EACH ROW`.
|
|
|
|
for (; $list->idx < $list->count; ++$list->idx) {
|
|
$token = $list->tokens[$list->idx];
|
|
$this->body[] = $token;
|
|
}
|
|
} else {
|
|
for (; $list->idx < $list->count; ++$list->idx) {
|
|
$token = $list->tokens[$list->idx];
|
|
if ($token->type === Token::TYPE_DELIMITER) {
|
|
break;
|
|
}
|
|
$this->body[] = $token;
|
|
}
|
|
}
|
|
}
|
|
}
|