diff --git a/src/PHPSQLParser/processors/TableProcessor.php b/src/PHPSQLParser/processors/TableProcessor.php index a163f418..155edf2c 100644 --- a/src/PHPSQLParser/processors/TableProcessor.php +++ b/src/PHPSQLParser/processors/TableProcessor.php @@ -40,6 +40,7 @@ */ namespace PHPSQLParser\processors; + use PHPSQLParser\utils\ExpressionType; /** @@ -49,41 +50,49 @@ * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) * */ -class TableProcessor extends AbstractProcessor { +class TableProcessor extends AbstractProcessor +{ - protected function getReservedType($token) { + protected function getReservedType($token) + { return array('expr_type' => ExpressionType::RESERVED, 'base_expr' => $token); } - protected function getConstantType($token) { + protected function getConstantType($token) + { return array('expr_type' => ExpressionType::CONSTANT, 'base_expr' => $token); } - protected function getOperatorType($token) { + protected function getOperatorType($token) + { return array('expr_type' => ExpressionType::OPERATOR, 'base_expr' => $token); } - protected function processPartitionOptions($tokens) { + protected function processPartitionOptions($tokens) + { $processor = new PartitionOptionsProcessor($this->options); return $processor->process($tokens); } - protected function processCreateDefinition($tokens) { + protected function processCreateDefinition($tokens) + { $processor = new CreateDefinitionProcessor($this->options); return $processor->process($tokens); } - protected function clear(&$expr, &$base_expr, &$category) { + protected function clear(&$expr, &$base_expr, &$category) + { $expr = array(); $base_expr = ''; $category = 'CREATE_DEF'; } - public function process($tokens) { + public function process($tokens) + { $currCategory = 'TABLE_NAME'; $result = array('base_expr' => false, 'name' => false, 'no_quotes' => false, 'create-def' => false, - 'options' => array(), 'like' => false, 'select-option' => false); + 'options' => array(), 'like' => false, 'select-option' => false); $expr = array(); $base_expr = ''; $skip = 0; @@ -108,252 +117,265 @@ public function process($tokens) { $upper = strtoupper($trim); switch ($upper) { - case ',': - // it is possible to separate the table options with comma! - if ($prevCategory === 'CREATE_DEF') { - $last = array_pop($result['options']); - $last['delim'] = ','; - $result['options'][] = $last; - $base_expr = ''; - } - continue 2; - - case 'UNION': - if ($prevCategory === 'CREATE_DEF') { - $expr[] = $this->getReservedType($trim); - $currCategory = 'UNION'; - continue 2; - } - break; - - case 'LIKE': - // like without parenthesis - if ($prevCategory === 'TABLE_NAME') { - $currCategory = $upper; - continue 2; - } - break; - - case '=': - // the optional operator - if ($prevCategory === 'TABLE_OPTION') { - $expr[] = $this->getOperatorType($trim); - continue 2; // don't change the category - } - break; - - case 'CHARACTER': - if ($prevCategory === 'CREATE_DEF') { - $expr[] = $this->getReservedType($trim); - $currCategory = 'TABLE_OPTION'; - } - if ($prevCategory === 'TABLE_OPTION') { - // add it to the previous DEFAULT - $expr[] = $this->getReservedType($trim); - continue 2; - } - break; - - case 'SET': - case 'CHARSET': - if ($prevCategory === 'TABLE_OPTION') { - // add it to a previous CHARACTER - $expr[] = $this->getReservedType($trim); - $currCategory = 'CHARSET'; - continue 2; - } - break; - - case 'COLLATE': - if ($prevCategory === 'TABLE_OPTION' || $prevCategory === 'CREATE_DEF') { - // add it to the previous DEFAULT - $expr[] = $this->getReservedType($trim); - $currCategory = 'COLLATE'; - continue 2; - } - break; - - case 'DIRECTORY': - if ($currCategory === 'INDEX_DIRECTORY' || $currCategory === 'DATA_DIRECTORY') { - // after INDEX or DATA - $expr[] = $this->getReservedType($trim); - continue 2; - } - break; - - case 'INDEX': - if ($prevCategory === 'CREATE_DEF') { - $expr[] = $this->getReservedType($trim); - $currCategory = 'INDEX_DIRECTORY'; - continue 2; - } - break; - - case 'DATA': - if ($prevCategory === 'CREATE_DEF') { - $expr[] = $this->getReservedType($trim); - $currCategory = 'DATA_DIRECTORY'; + case ',': + // it is possible to separate the table options with comma! + if ($prevCategory === 'CREATE_DEF') { + $last = array_pop($result['options']); + $last['delim'] = ','; + $result['options'][] = $last; + $base_expr = ''; + } continue 2; - } - break; - case 'INSERT_METHOD': - case 'DELAY_KEY_WRITE': - case 'ROW_FORMAT': - case 'PASSWORD': - case 'MAX_ROWS': - case 'MIN_ROWS': - case 'PACK_KEYS': - case 'CHECKSUM': - case 'COMMENT': - case 'CONNECTION': - case 'AUTO_INCREMENT': - case 'AVG_ROW_LENGTH': - case 'ENGINE': - case 'TYPE': - case 'STATS_AUTO_RECALC': - case 'STATS_PERSISTENT': - case 'KEY_BLOCK_SIZE': - if ($prevCategory === 'CREATE_DEF') { - $expr[] = $this->getReservedType($trim); - $currCategory = $prevCategory = 'TABLE_OPTION'; - continue 2; - } - break; + case 'UNION': + if ($prevCategory === 'CREATE_DEF') { + $expr[] = $this->getReservedType($trim); + $currCategory = 'UNION'; + continue 2; + } + break; - case 'DYNAMIC': - case 'FIXED': - case 'COMPRESSED': - case 'REDUNDANT': - case 'COMPACT': - case 'NO': - case 'FIRST': - case 'LAST': - case 'DEFAULT': - if ($prevCategory === 'CREATE_DEF') { - // DEFAULT before CHARACTER SET and COLLATE - $expr[] = $this->getReservedType($trim); - $currCategory = 'TABLE_OPTION'; - } - if ($prevCategory === 'TABLE_OPTION') { - // all assignments with the keywords - $expr[] = $this->getReservedType($trim); - $result['options'][] = array('expr_type' => ExpressionType::EXPRESSION, - 'base_expr' => trim($base_expr), 'delim' => ' ', 'sub_tree' => $expr); - $this->clear($expr, $base_expr, $currCategory); - } - break; + case 'LIKE': + // like without parenthesis + if ($prevCategory === 'TABLE_NAME') { + $currCategory = $upper; + continue 2; + } + break; - case 'IGNORE': - case 'REPLACE': - $expr[] = $this->getReservedType($trim); - $result['select-option'] = array('base_expr' => trim($base_expr), 'duplicates' => $trim, 'as' => false, - 'sub_tree' => $expr); - continue 2; - - case 'AS': - $expr[] = $this->getReservedType($trim); - if (!isset($result['select-option']['duplicates'])) { - $result['select-option']['duplicates'] = false; - } - $result['select-option']['as'] = true; - $result['select-option']['base_expr'] = trim($base_expr); - $result['select-option']['sub_tree'] = $expr; - continue 2; - - case 'PARTITION': - if ($prevCategory === 'CREATE_DEF') { - $part = $this->processPartitionOptions(array_slice($tokens, $tokenKey - 1, null, true)); - $skip = $part['last-parsed'] - $tokenKey; - $result['partition-options'] = $part['partition-options']; - continue 2; - } - // else - break; + case '=': + // the optional operator + if ($prevCategory === 'TABLE_OPTION') { + $expr[] = $this->getOperatorType($trim); + continue 2; // don't change the category + } + break; - default: - switch ($currCategory) { + case 'CHARACTER': + if ($prevCategory === 'CREATE_DEF') { + $expr[] = $this->getReservedType($trim); + $currCategory = 'TABLE_OPTION'; + } + if ($prevCategory === 'TABLE_OPTION') { + // add it to the previous DEFAULT + $expr[] = $this->getReservedType($trim); + continue 2; + } + break; + case 'SET': case 'CHARSET': - // the charset name - $expr[] = $this->getConstantType($trim); - $result['options'][] = array('expr_type' => ExpressionType::CHARSET, - 'base_expr' => trim($base_expr), 'delim' => ' ', 'sub_tree' => $expr); - $this->clear($expr, $base_expr, $currCategory); + if ($prevCategory === 'TABLE_OPTION') { + // add it to a previous CHARACTER + $expr[] = $this->getReservedType($trim); + $currCategory = 'CHARSET'; + continue 2; + } break; case 'COLLATE': - // the collate name - $expr[] = $this->getConstantType($trim); - $result['options'][] = array('expr_type' => ExpressionType::COLLATE, - 'base_expr' => trim($base_expr), 'delim' => ' ', 'sub_tree' => $expr); - $this->clear($expr, $base_expr, $currCategory); + if ($prevCategory === 'TABLE_OPTION' || $prevCategory === 'CREATE_DEF') { + // add it to the previous DEFAULT + $expr[] = $this->getReservedType($trim); + $currCategory = 'COLLATE'; + continue 2; + } break; - case 'DATA_DIRECTORY': - // we have the directory name - $expr[] = $this->getConstantType($trim); - $result['options'][] = array('expr_type' => ExpressionType::DIRECTORY, 'kind' => 'DATA', - 'base_expr' => trim($base_expr), 'delim' => ' ', 'sub_tree' => $expr); - $this->clear($expr, $base_expr, $prevCategory); - continue 3; - - case 'INDEX_DIRECTORY': - // we have the directory name - $expr[] = $this->getConstantType($trim); - $result['options'][] = array('expr_type' => ExpressionType::DIRECTORY, 'kind' => 'INDEX', - 'base_expr' => trim($base_expr), 'delim' => ' ', 'sub_tree' => $expr); - $this->clear($expr, $base_expr, $prevCategory); - continue 3; - - case 'TABLE_NAME': - $result['base_expr'] = $result['name'] = $trim; - $result['no_quotes'] = $this->revokeQuotation($trim); - $this->clear($expr, $base_expr, $prevCategory); + case 'DIRECTORY': + if ($currCategory === 'INDEX_DIRECTORY' || $currCategory === 'DATA_DIRECTORY') { + // after INDEX or DATA + $expr[] = $this->getReservedType($trim); + continue 2; + } break; - case 'LIKE': - $result['like'] = array('expr_type' => ExpressionType::TABLE, 'table' => $trim, - 'base_expr' => $trim, 'no_quotes' => $this->revokeQuotation($trim)); - $this->clear($expr, $base_expr, $currCategory); + case 'INDEX': + if ($prevCategory === 'CREATE_DEF') { + $expr[] = $this->getReservedType($trim); + $currCategory = 'INDEX_DIRECTORY'; + continue 2; + } break; - case '': - // after table name - if ($prevCategory === 'TABLE_NAME' && $upper[0] === '(' && substr($upper, -1) === ')') { - $unparsed = $this->splitSQLIntoTokens($this->removeParenthesisFromStart($trim)); - $coldef = $this->processCreateDefinition($unparsed); - $result['create-def'] = array('expr_type' => ExpressionType::BRACKET_EXPRESSION, - 'base_expr' => $base_expr, 'sub_tree' => $coldef['create-def']); - $expr = array(); - $base_expr = ''; - $currCategory = 'CREATE_DEF'; + case 'WITH': + if ($prevCategory === 'CREATE_DEF') { + $expr[] = $this->getReservedType($trim); + $currCategory = 'WITH'; + continue 2; + } + break; + case 'DATA': + if ($prevCategory === 'CREATE_DEF') { + $expr[] = $this->getReservedType($trim); + + if ($currCategory != 'WITH'){ + $currCategory = 'DATA_DIRECTORY'; + }else{ + $result['options'][] = array('expr_type' => ExpressionType::EXPRESSION, + 'base_expr' => trim($base_expr), 'delim' => ' ', 'sub_tree' => $expr); + $this->clear($expr, $base_expr, $currCategory); + } + continue 2; + } + break; + case 'INSERT_METHOD': + case 'DELAY_KEY_WRITE': + case 'ROW_FORMAT': + case 'PASSWORD': + case 'MAX_ROWS': + case 'MIN_ROWS': + case 'PACK_KEYS': + case 'CHECKSUM': + case 'COMMENT': + case 'CONNECTION': + case 'AUTO_INCREMENT': + case 'AVG_ROW_LENGTH': + case 'ENGINE': + case 'TYPE': + case 'STATS_AUTO_RECALC': + case 'STATS_PERSISTENT': + case 'KEY_BLOCK_SIZE': + if ($prevCategory === 'CREATE_DEF') { + $expr[] = $this->getReservedType($trim); + $currCategory = $prevCategory = 'TABLE_OPTION'; + continue 2; } break; - case 'UNION': - // TODO: this token starts and ends with parenthesis - // and contains a list of table names (comma-separated) - // split the token and add the list as subtree - // we must change the DefaultProcessor - - $unparsed = $this->splitSQLIntoTokens($this->removeParenthesisFromStart($trim)); - $expr[] = array('expr_type' => ExpressionType::BRACKET_EXPRESSION, 'base_expr' => $trim, - 'sub_tree' => '***TODO***'); - $result['options'][] = array('expr_type' => ExpressionType::UNION, 'base_expr' => trim($base_expr), - 'delim' => ' ', 'sub_tree' => $expr); - $this->clear($expr, $base_expr, $currCategory); + case 'DYNAMIC': + case 'FIXED': + case 'COMPRESSED': + case 'REDUNDANT': + case 'COMPACT': + case 'NO': + case 'FIRST': + case 'LAST': + case 'DEFAULT': + if ($prevCategory === 'CREATE_DEF') { + // DEFAULT before CHARACTER SET and COLLATE + $expr[] = $this->getReservedType($trim); + $currCategory = 'TABLE_OPTION'; + } + if ($prevCategory === 'TABLE_OPTION') { + // all assignments with the keywords + $expr[] = $this->getReservedType($trim); + $result['options'][] = array('expr_type' => ExpressionType::EXPRESSION, + 'base_expr' => trim($base_expr), 'delim' => ' ', 'sub_tree' => $expr); + $this->clear($expr, $base_expr, $currCategory); + } + break; + + case 'IGNORE': + case 'REPLACE': + $expr[] = $this->getReservedType($trim); + $result['select-option'] = array('base_expr' => trim($base_expr), 'duplicates' => $trim, 'as' => false, + 'sub_tree' => $expr); + continue 2; + + case 'AS': + $expr[] = $this->getReservedType($trim); + if (!isset($result['select-option']['duplicates'])) { + $result['select-option']['duplicates'] = false; + } + $result['select-option']['as'] = true; + $result['select-option']['base_expr'] = trim($base_expr); + $result['select-option']['sub_tree'] = $expr; + continue 2; + + case 'PARTITION': + if ($prevCategory === 'CREATE_DEF') { + $part = $this->processPartitionOptions(array_slice($tokens, $tokenKey - 1, null, true)); + $skip = $part['last-parsed'] - $tokenKey; + $result['partition-options'] = $part['partition-options']; + continue 2; + } + // else break; default: - // strings and numeric constants - $expr[] = $this->getConstantType($trim); - $result['options'][] = array('expr_type' => ExpressionType::EXPRESSION, - 'base_expr' => trim($base_expr), 'delim' => ' ', 'sub_tree' => $expr); - $this->clear($expr, $base_expr, $currCategory); + switch ($currCategory) { + + case 'CHARSET': + // the charset name + $expr[] = $this->getConstantType($trim); + $result['options'][] = array('expr_type' => ExpressionType::CHARSET, + 'base_expr' => trim($base_expr), 'delim' => ' ', 'sub_tree' => $expr); + $this->clear($expr, $base_expr, $currCategory); + break; + + case 'COLLATE': + // the collate name + $expr[] = $this->getConstantType($trim); + $result['options'][] = array('expr_type' => ExpressionType::COLLATE, + 'base_expr' => trim($base_expr), 'delim' => ' ', 'sub_tree' => $expr); + $this->clear($expr, $base_expr, $currCategory); + break; + + case 'DATA_DIRECTORY': + // we have the directory name + $expr[] = $this->getConstantType($trim); + $result['options'][] = array('expr_type' => ExpressionType::DIRECTORY, 'kind' => 'DATA', + 'base_expr' => trim($base_expr), 'delim' => ' ', 'sub_tree' => $expr); + $this->clear($expr, $base_expr, $prevCategory); + continue 3; + + case 'INDEX_DIRECTORY': + // we have the directory name + $expr[] = $this->getConstantType($trim); + $result['options'][] = array('expr_type' => ExpressionType::DIRECTORY, 'kind' => 'INDEX', + 'base_expr' => trim($base_expr), 'delim' => ' ', 'sub_tree' => $expr); + $this->clear($expr, $base_expr, $prevCategory); + continue 3; + + case 'TABLE_NAME': + $result['base_expr'] = $result['name'] = $trim; + $result['no_quotes'] = $this->revokeQuotation($trim); + $this->clear($expr, $base_expr, $prevCategory); + break; + + case 'LIKE': + $result['like'] = array('expr_type' => ExpressionType::TABLE, 'table' => $trim, + 'base_expr' => $trim, 'no_quotes' => $this->revokeQuotation($trim)); + $this->clear($expr, $base_expr, $currCategory); + break; + + case '': + // after table name + if ($prevCategory === 'TABLE_NAME' && $upper[0] === '(' && substr($upper, -1) === ')') { + $unparsed = $this->splitSQLIntoTokens($this->removeParenthesisFromStart($trim)); + $coldef = $this->processCreateDefinition($unparsed); + $result['create-def'] = array('expr_type' => ExpressionType::BRACKET_EXPRESSION, + 'base_expr' => $base_expr, 'sub_tree' => $coldef['create-def']); + $expr = array(); + $base_expr = ''; + $currCategory = 'CREATE_DEF'; + } + break; + + case 'UNION': + // TODO: this token starts and ends with parenthesis + // and contains a list of table names (comma-separated) + // split the token and add the list as subtree + // we must change the DefaultProcessor + + $unparsed = $this->splitSQLIntoTokens($this->removeParenthesisFromStart($trim)); + $expr[] = array('expr_type' => ExpressionType::BRACKET_EXPRESSION, 'base_expr' => $trim, + 'sub_tree' => '***TODO***'); + $result['options'][] = array('expr_type' => ExpressionType::UNION, 'base_expr' => trim($base_expr), + 'delim' => ' ', 'sub_tree' => $expr); + $this->clear($expr, $base_expr, $currCategory); + break; + + default: + // strings and numeric constants + $expr[] = $this->getConstantType($trim); + $result['options'][] = array('expr_type' => ExpressionType::EXPRESSION, + 'base_expr' => trim($base_expr), 'delim' => ' ', 'sub_tree' => $expr); + $this->clear($expr, $base_expr, $currCategory); + break; + } break; - } - break; } $prevCategory = $currCategory; @@ -373,4 +395,5 @@ public function process($tokens) { return $result; } } + ?> \ No newline at end of file diff --git a/tests/cases/parser/manticoreCasesTest.php b/tests/cases/parser/manticoreCasesTest.php index 04ebd51d..6b5afdc6 100644 --- a/tests/cases/parser/manticoreCasesTest.php +++ b/tests/cases/parser/manticoreCasesTest.php @@ -83,6 +83,7 @@ public function manticoreQueryProvider(): array ["CREATE MATERIALIZED VIEW view_table TO destination_kafka AS SELECT id, term as name, abbrev as short_name, UTC_TIMESTAMP() as received_at, GlossDef.size as size FROM kafka", 'ms_create_view_2'], ["CREATE TABLE destination_kafka (id bigint, name text, short_name text, received_at text, size multi) engine='columnar'", 'ms_create_table_1'], + ["CREATE TABLE new_table LIKE existing_table WITH DATA", 'ms_create_table_2'], ["SHOW TABLES", 'ms_show_tables_1'], ["SHOW TABLE abc", 'ms_show_tables_2'], ["SHOW TABLE `abc`", 'ms_show_tables_3'], diff --git a/tests/expected/parser/ms_create_table_2.serialized b/tests/expected/parser/ms_create_table_2.serialized new file mode 100644 index 00000000..9ae14ccb --- /dev/null +++ b/tests/expected/parser/ms_create_table_2.serialized @@ -0,0 +1 @@ +a:3:{s:6:"CREATE";a:4:{s:9:"expr_type";s:5:"table";s:10:"not-exists";b:0;s:9:"base_expr";s:5:"TABLE";s:8:"sub_tree";a:1:{i:0;a:2:{s:9:"expr_type";s:8:"reserved";s:9:"base_expr";s:5:"TABLE";}}}s:5:"TABLE";a:5:{s:9:"base_expr";s:9:"new_table";s:4:"name";s:9:"new_table";s:9:"no_quotes";a:2:{s:5:"delim";b:0;s:5:"parts";a:1:{i:0;s:9:"new_table";}}s:10:"create-def";b:0;s:7:"options";a:1:{i:0;a:4:{s:9:"expr_type";s:10:"expression";s:9:"base_expr";s:9:"WITH DATA";s:5:"delim";s:1:" ";s:8:"sub_tree";a:2:{i:0;a:2:{s:9:"expr_type";s:8:"reserved";s:9:"base_expr";s:4:"WITH";}i:1;a:2:{s:9:"expr_type";s:8:"reserved";s:9:"base_expr";s:4:"DATA";}}}}}s:4:"LIKE";a:4:{s:9:"expr_type";s:5:"table";s:5:"table";s:14:"existing_table";s:9:"base_expr";s:14:"existing_table";s:9:"no_quotes";a:2:{s:5:"delim";b:0;s:5:"parts";a:1:{i:0;s:14:"existing_table";}}}} \ No newline at end of file