diff options
author | Dan Ungureanu <udan1107@gmail.com> | 2015-06-28 23:55:20 +0300 |
---|---|---|
committer | Dan Ungureanu <udan1107@gmail.com> | 2015-06-28 23:55:20 +0300 |
commit | 5f15789bd325c4fcd6642ecfeea18a5b639acc99 (patch) | |
tree | 472815cc0139c5fbed907e8465de3cd0d129f9cf /src | |
parent | 7780defae68f630dd49dccd88f898093a62da510 (diff) | |
download | sql-parser-5f15789bd325c4fcd6642ecfeea18a5b639acc99.zip sql-parser-5f15789bd325c4fcd6642ecfeea18a5b639acc99.tar.gz sql-parser-5f15789bd325c4fcd6642ecfeea18a5b639acc99.tar.bz2 |
Improved parsing system.
Better support for current statements.
Improved query utilities.
Fixed a bug in LIMIT's parser.
Fixed a documentation issue.
Diffstat (limited to 'src')
-rw-r--r-- | src/Fragments/LimitKeyword.php | 4 | ||||
-rw-r--r-- | src/Parser.php | 10 | ||||
-rw-r--r-- | src/Statement.php | 11 | ||||
-rw-r--r-- | src/Statements/AlterStatement.php | 105 | ||||
-rw-r--r-- | src/Statements/DropStatement.php | 4 | ||||
-rw-r--r-- | src/Statements/NotImplementedStatement.php | 4 | ||||
-rw-r--r-- | src/Utils/Query.php | 99 |
7 files changed, 199 insertions, 38 deletions
diff --git a/src/Fragments/LimitKeyword.php b/src/Fragments/LimitKeyword.php index 261ef3e..a23eb4c 100644 --- a/src/Fragments/LimitKeyword.php +++ b/src/Fragments/LimitKeyword.php @@ -70,6 +70,10 @@ class LimitKeyword extends Fragment 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.'); diff --git a/src/Parser.php b/src/Parser.php index 0a876b7..2a9bd47 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -79,7 +79,11 @@ class Parser * @var array */ public static $KEYWORD_PARSERS = array( - + 'ALTER' => array( + 'class' => 'SqlParser\\Fragments\\FieldFragment', + 'field' => 'table', + 'options' => array('skipColumn' => true), + ), 'ANALYZE' => array( 'class' => 'SqlParser\\Fragments\\FromKeyword', 'field' => 'tables', @@ -100,6 +104,10 @@ class Parser 'class' => 'SqlParser\\Fragments\\FromKeyword', 'field' => 'tables', ), + 'DROP' => array( + 'class' => 'SqlParser\\Fragments\\FromKeyword', + 'field' => 'fields' + ), 'FROM' => array( 'class' => 'SqlParser\\Fragments\\FromKeyword', 'field' => 'from', diff --git a/src/Statement.php b/src/Statement.php index 74b2cc3..f10b5ee 100644 --- a/src/Statement.php +++ b/src/Statement.php @@ -100,9 +100,18 @@ abstract class Statement */ $field = null; + /** + * Parser's options. + * @var array + */ + $options = array(); + if (!empty(Parser::$KEYWORD_PARSERS[$token->value])) { $class = Parser::$KEYWORD_PARSERS[$token->value]['class']; $field = Parser::$KEYWORD_PARSERS[$token->value]['field']; + if (!empty(Parser::$KEYWORD_PARSERS[$token->value]['options'])) { + $options = Parser::$KEYWORD_PARSERS[$token->value]['options']; + } } if (!empty(Parser::$STATEMENT_PARSERS[$token->value])) { @@ -130,7 +139,7 @@ abstract class Statement // Parsing this keyword. if ($class !== null) { ++$list->idx; // Skipping keyword. - $this->$field = $class::parse($parser, $list, array()); + $this->$field = $class::parse($parser, $list, $options); } $this->after($parser, $list, $token); diff --git a/src/Statements/AlterStatement.php b/src/Statements/AlterStatement.php index 1d6b44c..b6c18ed 100644 --- a/src/Statements/AlterStatement.php +++ b/src/Statements/AlterStatement.php @@ -8,6 +8,11 @@ */ namespace SqlParser\Statements; +use SqlParser\Parser; +use SqlParser\Token; +use SqlParser\TokensList; +use SqlParser\Fragments\FieldFragment; +use SqlParser\Fragments\OptionsFragment; use SqlParser\Statements\NotImplementedStatement; /** @@ -23,23 +28,101 @@ class AlterStatement extends NotImplementedStatement { /** + * Table affected. + * + * @var FieldFragment + */ + public $table; + + /** + * Column affected by this statement. + * + * @var FieldFragment + */ + public $altered; + + /** * Options of this statement. * * @var array */ public static $OPTIONS = array( - 'DATABASE' => 1, - 'EVENT' => 1, - 'FUNCTION' => 1, - 'INDEX' => 1, - 'LOGFILE' => 1, - 'PROCEDURE' => 1, - 'SCHEMA' => 1, - 'SERVER' => 1, - 'TABLE' => 1, - 'TABLESPACE' => 1, - 'TRIGGER' => 1, + 'ONLINE' => 1, + 'OFFLINE' => 1, + + 'IGNORE' => 2, + + 'TABLE' => 3, + + 'ADD' => 4, + 'ALTER' => 4, + 'ANALYZE' => 4, + 'CHANGE' => 4, + 'CHECK' => 4, + 'COALESCE' => 4, + 'CONVERT' => 4, + 'DISABLE' => 4, + 'DISCARD' => 4, + 'DROP' => 4, + 'ENABLE' => 4, + 'IMPORT' => 4, + 'MODIFY' => 4, + 'OPTIMIZE' => 4, + 'ORDER' => 4, + 'PARTITION' => 4, + 'REBUILD' => 4, + 'REMOVE' => 4, + 'RENAME' => 4, + 'REORGANIZE' => 4, + 'REPAIR' => 4, + + 'COLUMN' => 5, + 'CONSTRAINT' => 5, + 'DEFAULT' => 5, + 'TO' => 5, + 'BY' => 5, + 'FOREIGN' => 5, + 'FULLTEXT' => 5, + 'KEYS' => 5, + 'PARTITIONING' => 5, + 'PRIMARY KEY' => 5, + 'SPATIAL' => 5, + 'TABLESPACE' => 5, + 'INDEX' => 5, + + 'DEFAULT CHARACTER SET' => array(6, 'var'), + + 'COLLATE' => array(7, 'var'), ); + /** + * Extracts the name of affected column. + * + * @param Parser $parser The instance that requests parsing. + * @param TokensList $list The list of tokens to be parsed. + * @param Token $token The token that is being parsed. + * + * @return void + */ + public function after(Parser $parser, TokensList $list, Token $token) + { + // Parsing operation. + ++$list->idx; + $this->options->merge( + OptionsFragment::parse( + $parser, + $list, + static::$OPTIONS + ) + ); + + // Parsing affected field. + ++$list->idx; + $this->altered = FieldFragment::parse($parser, $list); + + // + parent::after($parser, $list, $token); + } + } diff --git a/src/Statements/DropStatement.php b/src/Statements/DropStatement.php index 716ca54..babf18a 100644 --- a/src/Statements/DropStatement.php +++ b/src/Statements/DropStatement.php @@ -48,7 +48,7 @@ class DropStatement extends Statement /** * Dropped elements. * - * @var FromKeyworrd[] + * @var FieldFragment[] */ - public $name; + public $fields; } diff --git a/src/Statements/NotImplementedStatement.php b/src/Statements/NotImplementedStatement.php index 1ad5cc5..1689df2 100644 --- a/src/Statements/NotImplementedStatement.php +++ b/src/Statements/NotImplementedStatement.php @@ -16,7 +16,7 @@ use SqlParser\TokensList; /** * Not implemented (yet) statements. * - * The `before` function makes the parser jump straight to the first delimiter. + * The `after` function makes the parser jump straight to the first delimiter. * * @category Statements * @package SqlParser @@ -36,7 +36,7 @@ class NotImplementedStatement extends Statement * * @return void */ - public function before(Parser $parser, TokensList $list, Token $token) + public function after(Parser $parser, TokensList $list, Token $token) { $list->getNextOfType(Token::TYPE_DELIMITER); --$list->idx; diff --git a/src/Utils/Query.php b/src/Utils/Query.php index 8cb9dfd..dde3304 100644 --- a/src/Utils/Query.php +++ b/src/Utils/Query.php @@ -373,6 +373,10 @@ class Query if ($statement instanceof SelectStatement) { $ret['select_tables'] = array(); $ret['select_expr'] = array(); + + // Trying to find selected tables only from the select expression. + // Sometimes, this is not possible because the tables aren't defined + // explicitly (e.g. SELECT * FROM film, SELECT film_id FROM film). foreach ($statement->expr as $expr) { if (!empty($expr->table)) { $ret['select_tables'][] = array( @@ -383,16 +387,37 @@ class Query $ret['select_expr'][] = $expr->expr; } } + + // If no tables names were found in the SELECT clause or if there + // are expressions like * or COUNT(*), etc. tables names should be + // extracted from the FROM clause as well. + if ((empty($ret['select_tables'])) || (!$ret['select_expr'])) { + foreach ($statement->from as $expr) { + if (!empty($expr->table)) { + $ret['select_tables'][] = array( + $expr->table, + !empty($expr->database) ? $expr->database : null + ); + } + } + } } return $ret; } + /** + * Gets the type of clause. + * + * @param string $clause The clause. + * + * @return string + */ public static function getClauseType($clause) { $type = ''; for ($i = 0, $len = strlen($clause); $i < $len; ++$i) { - if ((empty($type)) && (ctype_space($type))) { + if ((empty($type)) && (ctype_space($clause[$i]))) { // Skipping whitespaces if we haven't started determining the // type. continue; @@ -408,18 +433,20 @@ class Query } /** - * Replaces the clause in the query. If the clause does not exist, it is - * inserted. - * - * It is a very basic version of a query builder. + * Gets a specific clause. * * @param Statement $statement The parsed query that has to be modified. * @param TokensList $list The list of tokens. - * @param string $clause The clause to be replaced. + * @param string $clause The clause to be returned. + * @param int $type The type of the search. + * -1 for everything that was before + * 0 only for the clause + * 1 for everything after + * @param bool $skipFirst Whether to skip the first keyword in clause. * * @return string */ - public static function replaceClause($statement, $list, $clause) + public static function getClause($statement, $list, $clause, $type = 0, $skipFirst = true) { /** @@ -436,16 +463,10 @@ class Query $brackets = 0; /** - * The sections before the clause - * @var string - */ - $before = ''; - - /** - * The sections after the clause. + * The string to be returned. * @var string */ - $after = ''; + $ret = ''; /** * The place where the clause should be added. @@ -473,21 +494,57 @@ class Query // Checking if we changed sections. if ($token->type === Token::TYPE_KEYWORD) { if (isset($statement::$SECTIONS[$token->value])) { - if ($statement::$SECTIONS[$token->value] > $currIdx) { + if ($statement::$SECTIONS[$token->value] >= $currIdx) { $currIdx = $statement::$SECTIONS[$token->value]; + if (($skipFirst) && ($currIdx == $clauseIdx)) { + // This token is skipped (not added to the old + // clause) because it will be replaced. + continue; + } } } } } - if ($currIdx < $clauseIdx) { - $before .= $token->token; - } elseif ($currIdx > $clauseIdx) { - $after .= $token->value; + if ((($type === -1) && ($currIdx < $clauseIdx)) + || (($type === 0) && ($currIdx === $clauseIdx)) + || (($type === 1) && ($currIdx > $clauseIdx)) + ) { + $ret .= $token->token; } } - return $before . ' ' . $clause . ' ' . $after; + return trim($ret); + } + + /** + * Builds a query by rebuilding the statement from the tokens list supplied + * and replaces a clause. + * + * It is a very basic version of a query builder. + * + * @param Statement $statement The parsed query that has to be modified. + * @param TokensList $list The list of tokens. + * @param string $clause The clause to be replaced. + * @param bool $onlyType Whether only the type of the clause should + * be replaced or the entire clause. + * + * @return string + */ + public static function replaceClause($statement, $list, $clause, $onlyType = false) + { + // TODO: Update the tokens list and the statement. + + if ($onlyType) { + return static::getClause($statement, $list, $clause, -1, false) . ' ' . + $clause . ' ' . + static::getCLause($statement, $list, $clause, 0) . ' ' . + static::getClause($statement, $list, $clause, 1, false); + } + + return static::getClause($statement, $list, $clause, -1, false) . ' ' . + $clause . ' ' . + static::getClause($statement, $list, $clause, 1, false); } } |