2 declare(strict_types = 1);
19 use Doctrine\DBAL\Schema\Table;
87 public function match($token)
89 $lookaheadType = $this->lexer->lookahead[
'type'];
92 if ($lookaheadType !== $token) {
117 $this->lexer->moveNext();
126 public function free($deep =
false, $position = 0)
129 $this->lexer->resetPosition($position);
133 $this->lexer->resetPeek();
136 $this->lexer->token =
null;
137 $this->lexer->lookahead =
null;
149 public function parse(): array
158 $table = $tableBuilder->create($ast);
172 public function syntaxError($expected =
'', $token =
null)
174 if ($token ===
null) {
175 $token = $this->lexer->lookahead;
178 $tokenPos = $token[
'position'] ??
'-1';
180 $message =
"line 0, col {$tokenPos}: Error: ";
181 $message .= ($expected !==
'') ?
"Expected {$expected}, got " :
'Unexpected ';
182 $message .= ($this->lexer->lookahead ===
null) ?
'end of string.' :
"'{$token['value']}'";
198 if ($token ===
null) {
199 $token = $this->lexer->lookahead;
207 $length = strlen($createTableStatement);
208 $pos = $token[
'position'] + $distance;
209 $pos = strpos($createTableStatement,
' ', ($length > $pos) ? $pos : $length);
210 $length = ($pos !==
false) ? $pos - $token[
'position'] : $distance;
212 $tokenPos = array_key_exists(
'position', $token) && $token[
'position'] > 0 ? $token[
'position'] :
'-1';
213 $tokenStr = substr($createTableStatement, $token[
'position'], $length);
216 $message =
'line 0, col ' . $tokenPos .
" near '" . $tokenStr .
"': Error: " . $message;
230 $token = $this->lexer->peek();
233 while ($numUnmatched > 0 && $token !==
null) {
234 switch ($token[
'type']) {
245 $token = $this->lexer->peek();
249 $this->lexer->resetPeek();
263 $this->lexer->moveNext();
272 if ($this->lexer->lookahead !==
null) {
291 switch ($this->lexer->lookahead[
'type']) {
317 $createTableStatement->tableOptions = $this->
tableOptions();
319 return $createTableStatement;
330 $isTemporary =
false;
370 $createDefinitions = [];
405 $definitionItem =
null;
407 switch ($this->lexer->lookahead[
'type']) {
425 $this->
semanticalError(
'CONSTRAINT [symbol] index definition part not supported');
434 return $definitionItem;
452 switch ($this->lexer->lookahead[
'type']) {
471 $this->lexer->moveNext();
479 $this->lexer->moveNext();
487 $this->lexer->moveNext();
492 $this->
syntaxError(
'PRIMARY, KEY, INDEX, UNIQUE, FULLTEXT or SPATIAL');
496 if (!$indexDefinition->isPrimary) {
509 if (!$isFulltext && !$isSpatial) {
510 $indexDefinition->indexType = $this->
indexType();
526 return $indexDefinition;
560 return $foreignKeyDefinition;
595 switch ($this->lexer->lookahead[
'type']) {
598 $indexType =
'BTREE';
625 switch ($this->lexer->lookahead[
'type']) {
631 $this->lexer->moveNext();
632 $options[
'key_block_size'] = (int)$this->lexer->token[
'value'];
635 $options[
'index_type'] = $this->indexType();
645 $options[
'comment'] = $this->lexer->token[
'value'];
648 $this->
syntaxError(
'KEY_BLOCK_SIZE, USING, WITH PARSER or COMMENT');
677 switch ($this->lexer->lookahead[
'type']) {
679 $columnDefinitionItem->allowNull =
false;
684 $columnDefinitionItem->null =
true;
688 $columnDefinitionItem->hasDefaultValue =
true;
692 $columnDefinitionItem->autoIncrement =
true;
696 $columnDefinitionItem->unique =
true;
703 $columnDefinitionItem->primary =
true;
710 $columnDefinitionItem->index =
true;
716 $columnDefinitionItem->comment = $this->lexer->lookahead[
'value'];
723 $columnDefinitionItem->columnFormat =
'fixed';
726 $columnDefinitionItem->columnFormat =
'dynamic';
735 $columnDefinitionItem->storage =
'memory';
738 $columnDefinitionItem->storage =
'disk';
749 'NOT, NULL, DEFAULT, AUTO_INCREMENT, UNIQUE, ' .
750 'PRIMARY, COMMENT, COLUMN_FORMAT, STORAGE or REFERENCES'
755 return $columnDefinitionItem;
799 switch ($this->lexer->lookahead[
'type']) {
975 'BIT, TINYINT, SMALLINT, MEDIUMINT, INT, INTEGER, BIGINT, REAL, DOUBLE, FLOAT, DECIMAL, NUMERIC, ' .
976 'DATE, TIME, TIMESTAMP, DATETIME, YEAR, CHAR, VARCHAR, BINARY, VARBINARY, TINYBLOB, BLOB, ' .
977 'MEDIUMBLOB, LONGBLOB, TINYTEXT, TEXT, MEDIUMTEXT, LONGTEXT, ENUM, SET, or JSON'
995 switch ($this->lexer->lookahead[
'type']) {
997 $value = (int)$this->lexer->lookahead[
'value'];
1000 $value = (
float)$this->lexer->lookahead[
'value'];
1003 $value = (string)$this->lexer->lookahead[
'value'];
1006 $value =
'CURRENT_TIMESTAMP';
1012 $this->syntaxError(
'String, Integer, Float, NULL or CURRENT_TIMESTAMP');
1015 $this->lexer->moveNext();
1032 $this->
semanticalError(
'The current data type requires a field length definition.');
1038 $length = (int)$this->lexer->lookahead[
'value'];
1059 $options[
'length'] = (int)$this->lexer->lookahead[
'value'];
1064 $options[
'decimals'] = (int)$this->lexer->lookahead[
'value'];
1081 $options = [
'unsigned' =>
false,
'zerofill' =>
false];
1088 switch ($this->lexer->lookahead[
'type']) {
1091 $options[
'unsigned'] =
true;
1095 $options[
'zerofill'] =
true;
1114 if ($fractionalSecondsPart < 0) {
1115 $this->
semanticalError(
'the fractional seconds part for TIME, DATETIME or TIMESTAMP columns must >= 0');
1117 if ($fractionalSecondsPart > 6) {
1118 $this->
semanticalError(
'the fractional seconds part for TIME, DATETIME or TIMESTAMP columns must <= 6');
1121 return $fractionalSecondsPart;
1132 $options = [
'binary' =>
false,
'charset' =>
null,
'collation' =>
null];
1139 switch ($this->lexer->lookahead[
'type']) {
1142 $options[
'binary'] =
true;
1148 $options[
'charset'] = $this->lexer->token[
'value'];
1153 $options[
'collation'] = $this->lexer->token[
'value'];
1156 $this->
syntaxError(
'BINARY, CHARACTER SET or COLLATE');
1171 $options = [
'charset' =>
null,
'collation' =>
null];
1178 switch ($this->lexer->lookahead[
'type']) {
1183 $options[
'charset'] = $this->lexer->token[
'value'];
1188 $options[
'collation'] = $this->lexer->token[
'value'];
1231 return (
string)$this->lexer->token[
'value'];
1249 $referenceColumns = [];
1262 switch ($this->lexer->lookahead[
'type']) {
1265 $referenceDefinition->match = $this->lexer->lookahead[
'value'];
1266 $this->lexer->moveNext();
1283 return $referenceDefinition;
1303 $direction =
'DESC';
1319 switch ($this->lexer->lookahead[
'type']) {
1322 $action =
'RESTRICT';
1326 $action =
'CASCADE';
1331 $action =
'SET NULL';
1336 $action =
'NO ACTION';
1339 $this->
syntaxError(
'RESTRICT, CASCADE, SET NULL or NO ACTION');
1382 switch ($this->lexer->lookahead[
'type']) {
1419 if (!in_array($options[
'compression'], [
'ZLIB',
'LZ4',
'NONE'],
true)) {
1420 $this->
syntaxError(
'ZLIB, LZ4 or NONE', $this->lexer->token);
1439 if (!in_array($options[
'encryption'], [
'Y',
'N'],
true)) {
1451 if (!in_array($options[
'insert_method'], [
'NO',
'FIRST',
'LAST'],
true)) {
1452 $this->
syntaxError(
'NO, FIRST or LAST', $this->lexer->token);
1470 if (!in_array($options[
'pack_keys'], [
'0',
'1',
'DEFAULT'],
true)) {
1471 $this->
syntaxError(
'0, 1 or DEFAULT', $this->lexer->token);
1481 $validRowFormats = [
'DEFAULT',
'DYNAMIC',
'FIXED',
'COMPRESSED',
'REDUNDANT',
'COMPACT'];
1482 if (!in_array($options[
'row_format'], $validRowFormats,
true)) {
1484 'DEFAULT, DYNAMIC, FIXED, COMPRESSED, REDUNDANT, COMPACT',
1492 if (!in_array($options[
'stats_auto_recalc'], [
'0',
'1',
'DEFAULT'],
true)) {
1493 $this->
syntaxError(
'0, 1 or DEFAULT', $this->lexer->token);
1499 if (!in_array($options[
'stats_persistent'], [
'0',
'1',
'DEFAULT'],
true)) {
1500 $this->
syntaxError(
'0, 1 or DEFAULT', $this->lexer->token);
1505 $options[
'stats_sample_pages'] = strtoupper((
string)$this->
tableOptionValue());
1506 if (!in_array($options[
'stats_sample_pages'], [
'0',
'1',
'DEFAULT'],
true)) {
1507 $this->
syntaxError(
'0, 1 or DEFAULT', $this->lexer->token);
1516 'DEFAULT, ENGINE, AUTO_INCREMENT, AVG_ROW_LENGTH, CHARACTER SET, ' .
1517 'CHECKSUM, COLLATE, COMMENT, COMPRESSION, CONNECTION, DATA DIRECTORY, ' .
1518 'DELAY_KEY_WRITE, ENCRYPTION, INDEX DIRECTORY, INSERT_METHOD, KEY_BLOCK_SIZE, ' .
1519 'MAX_ROWS, MIN_ROWS, PACK_KEYS, PASSWORD, ROW_FORMAT, STATS_AUTO_RECALC, ' .
1520 'STATS_PERSISTENT, STATS_SAMPLE_PAGES or TABLESPACE'
1540 $this->lexer->moveNext();
1542 return $this->lexer->token[
'value'];
1554 $schemaObjectName = $this->lexer->lookahead[
'value'];
1555 $this->lexer->moveNext();