diff options
-rw-r--r-- | src/Builder.php | 84 | ||||
-rw-r--r-- | src/Fragments/CreateDefFragment.php | 125 | ||||
-rw-r--r-- | src/Fragments/FieldFragment.php | 7 | ||||
-rw-r--r-- | src/Parser.php | 6 | ||||
-rw-r--r-- | src/Statement.php | 84 | ||||
-rw-r--r-- | src/Statements/CreateStatement.php | 143 | ||||
-rw-r--r-- | src/TokensList.php | 10 | ||||
-rw-r--r-- | tests/Builder/StatementTest.php | 4 |
8 files changed, 223 insertions, 240 deletions
diff --git a/src/Builder.php b/src/Builder.php deleted file mode 100644 index b4559e9..0000000 --- a/src/Builder.php +++ /dev/null @@ -1,84 +0,0 @@ -<?php - -/** - * This is one of the most important components, along with the lexer and the - * parser. - * - * @package SqlParser - */ -namespace SqlParser; - -use SqlParser\Exceptions\ParserException; - -/** - * Builds the string representation of a Statement. - * - * @category Parser - * @package SqlParser - * @author Dan Ungureanu <udan1107@gmail.com> - * @license http://opensource.org/licenses/GPL-2.0 GNU Public License - */ -class Builder -{ - - /** - * Statement to be build. - * - * @var Statement - */ - public $statement; - - /** - * Built query. - * - * @var string - */ - public $query; - - /** - * Constructor. - * - * @param Statement $statement - */ - public function __construct($statement = null) - { - $this->statement = $statement; - - if ($this->statement != null) { - $this->build(); - } - } - - /** - * Builds the statement. - * - * @return void - */ - public function build() - { - $statement = $this->statement; - foreach ($statement::$CLAUSES as $clause) { - $name = $clause[0]; - $type = $clause[1]; - - if (empty(Parser::$KEYWORD_PARSERS[$name])) { - continue; - } - - $class = Parser::$KEYWORD_PARSERS[$name]['class']; - $field = Parser::$KEYWORD_PARSERS[$name]['field']; - - if (empty($statement->$field)) { - continue; - } - - if ($type & 2) { - $this->query .= $name . ' '; - } - - if ($type & 1) { - $this->query .= $class::build($statement->$field) . ' '; - } - } - } -} diff --git a/src/Fragments/CreateDefFragment.php b/src/Fragments/CreateDefFragment.php deleted file mode 100644 index 933ea5e..0000000 --- a/src/Fragments/CreateDefFragment.php +++ /dev/null @@ -1,125 +0,0 @@ -<?php - -/** - * Parses the definition that follows the `CREATE` keyword. - * - * @package SqlParser - * @subpackage Fragments - */ -namespace SqlParser\Fragments; - -use SqlParser\Fragment; -use SqlParser\Parser; -use SqlParser\Token; -use SqlParser\TokensList; - -/** - * Parses the definition that follows the `CREATE` keyword. - * - * @category Fragments - * @package SqlParser - * @subpackage Fragments - * @author Dan Ungureanu <udan1107@gmail.com> - * @license http://opensource.org/licenses/GPL-2.0 GNU Public License - */ -class CreateDefFragment extends Fragment -{ - - /** - * 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'), - 'DEFAULT CHARACTER SET' => array(4, 'var'), - 'CHARACTER SET' => array(4, 'var'), - 'CHECKSUM' => array(5, 'var'), - 'DEFAULT COLLATE' => array(5, '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, - 'CONSTAINS SQL' => 4, - 'NO SQL' => 4, - 'READS SQL DATA' => 4, - 'MODIFIES SQL DATA' => 4, - 'SQL SEQURITY DEFINER' => array(5, 'var'), - ); - - /** - * The name of the new table. - * - * @var string - */ - public $name; - - /** - * @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 CreateDefFragment - */ - public static function parse(Parser $parser, TokensList $list, array $options = array()) - { - $ret = new CreateDefFragment(); - - for (; $list->idx < $list->count; ++$list->idx) { - /** - * Token parsed at this moment. - * @var 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_OPERATOR) - && ($token->value === '(') - ) { - break; - } - - $ret->name .= $token->value; - } - - --$list->idx; - return $ret; - } -} diff --git a/src/Fragments/FieldFragment.php b/src/Fragments/FieldFragment.php index a6c5777..edd0a2b 100644 --- a/src/Fragments/FieldFragment.php +++ b/src/Fragments/FieldFragment.php @@ -177,7 +177,7 @@ class FieldFragment extends Fragment if (($token->type === Token::TYPE_KEYWORD) && ($token->flags & Token::FLAG_KEYWORD_RESERVED)) { // Keywords may be found only between brackets. if ($brackets === 0) { - if ($token->value === 'AS') { + if ((empty($options['noAlias'])) && ($token->value === 'AS')) { $alias = 2; continue; } @@ -194,6 +194,11 @@ class FieldFragment extends Fragment } if ($token->type === Token::TYPE_OPERATOR) { + if ((!empty($options['noBrackets'])) && + (($token->value === '(') || ($token->value === ')')) + ) { + break; + } if ($token->value === '(') { ++$brackets; // We don't check to see if `$prev` is `true` (open bracke diff --git a/src/Parser.php b/src/Parser.php index 29e6701..11874fa 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -339,15 +339,15 @@ class Parser */ $stmt = new $class(); + // Parsing the actual statement. + $stmt->parse($this, $this->list); + // The first token that is a part of this token is the next token // unprocessed by the previous statement. // There might be brackets around statements and this shouldn't // affect the parser $stmt->first = $prevLastIdx + 1; - // Parsing the actual statement. - $stmt->parse($this, $this->list); - // Storing the index of the last token parsed and updating the old // index. $stmt->last = $list->idx; diff --git a/src/Statement.php b/src/Statement.php index 0cb362c..1591d66 100644 --- a/src/Statement.php +++ b/src/Statement.php @@ -64,6 +64,84 @@ abstract class Statement public $last; /** + * Constructor. + * + * @param Parser $parser The instance that requests parsing. + * @param TokensList $list The list of tokens to be parsed. + */ + public function __construct(Parser $parser = null, TokensList $list = null) + { + if (($parser !== null) && ($list !== null)) { + $this->parse($parser, $list); + } + } + + /** + * Builds the statement. + * + * @return void + */ + public function build() + { + /** + * Query to be returned. + * @var string + */ + $query = ''; + + foreach (static::$CLAUSES as $clause) { + + /** + * The name of the clause. + * @var string + */ + $name = $clause[0]; + + /** + * The type of the clause. + * @see self::$CLAUSES + * @var int + */ + $type = $clause[1]; + + // Checking if there is any parser (builder) for this clause. + if (empty(Parser::$KEYWORD_PARSERS[$name])) { + continue; + } + + /** + * The builder (parser) of this clause. + * @var string + */ + $class = Parser::$KEYWORD_PARSERS[$name]['class']; + + /** + * The name of the field that is used as source for the builder. + * Same field is used to store the result of parsing. + * @var string + */ + $field = Parser::$KEYWORD_PARSERS[$name]['field']; + + // The field is empty, there is nothing to be built. + if (empty($this->$field)) { + continue; + } + + // Checking if the name of the clause should be added. + if ($type & 2) { + $query .= $name . ' '; + } + + // Checking if the result of the builder should be added. + if ($type & 1) { + $query .= $class::build($this->$field) . ' '; + } + } + + return $query; + } + + /** * Parses the statements defined by the tokens list. * * @param Parser $parser The instance that requests parsing. @@ -73,6 +151,9 @@ abstract class Statement */ public function parse(Parser $parser, TokensList $list) { + // This may be corrected by the parser. + $this->first = $list->idx; + /** * Whether options were parsed or not. * For statements that do not have any options this is set to `true` by @@ -162,7 +243,8 @@ abstract class Statement $this->after($parser, $list, $token); } - --$list->idx; // Go back to last used token. + // This may be corrected by the parser. + $this->last = --$list->idx; // Go back to last used token. } /** diff --git a/src/Statements/CreateStatement.php b/src/Statements/CreateStatement.php index f13866a..7fd8ef0 100644 --- a/src/Statements/CreateStatement.php +++ b/src/Statements/CreateStatement.php @@ -12,9 +12,10 @@ use SqlParser\Parser; use SqlParser\Statement; use SqlParser\Token; use SqlParser\TokensList; -use SqlParser\Fragments\CreateDefFragment; +use SqlParser\Fragments\ArrayFragment; use SqlParser\Fragments\DataTypeFragment; use SqlParser\Fragments\FieldDefFragment; +use SqlParser\Fragments\FieldFragment; use SqlParser\Fragments\OptionsFragment; use SqlParser\Fragments\ParamDefFragment; @@ -49,38 +50,113 @@ class CreateStatement extends Statement 'USER' => 1, 'VIEW' => 1, + // CREATE TABLE 'TEMPORARY' => 2, 'IF NOT EXISTS' => 3, - 'DEFINER' => array(4, 'var'), + + // CREATE FUNCTION / PROCEDURE and CREATE VIEW + 'DEFINER' => array(2, 'var'), + + // CREATE VIEW + 'OR REPLACE' => array(3, 'var'), + 'ALGORITHM' => array(4, 'var'), + 'DEFINER' => array(5, 'var'), + 'SQL SECURITY' => array(6, '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'), + 'DEFAULT CHARACTER SET' => array(4, 'var'), + 'CHARACTER SET' => array(4, 'var'), + 'CHECKSUM' => array(5, 'var'), + 'DEFAULT COLLATE' => array(5, '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, + 'CONSTAINS SQL' => 4, + 'NO SQL' => 4, + 'READS SQL DATA' => 4, + 'MODIFIES SQL DATA' => 4, + 'SQL SEQURITY DEFINER' => array(5, 'var'), ); /** - * The name of the enw table. + * The name of the entity that is created. * - * @var CreateDefFragment + * Used by all `CREATE` statements. + * + * @var FieldFragment */ public $name; /** * The options of the entity (table, procedure, function, etc.). * + * Used by `CREATE TABLE`, `CREATE FUNCTION` and `CREATE PROCEDURE`. + * * @var OptionsFragment * - * @see CreateDefFragment::$TABLE_OPTIONS - * @see CreateDefFragment::$FUNC_OPTIONS + * @see static::$TABLE_OPTIONS + * @see static::$FUNC_OPTIONS */ public $entityOptions; /** - * Field created by this statement. + * If `CREATE TABLE`, a list of fields in the new table. + * If `CREATE VIEW`, a list of columns. * - * @var FieldDefFragment[] + * Used by `CREATE TABLE` and `CREATE VIEW`. + * + * @var FieldDefFragment[]|ArrayFragment */ public $fields; /** + * The `SELECT` statement that defines this view. + * + * Used by `CREATE VIEW`. + * + * @var SelectStatement + */ + public $select; + + /** * The return data type of this routine. * + * Used by `CREATE FUNCTION`. + * * @var DataTypeFragment */ public $return; @@ -88,45 +164,59 @@ class CreateStatement extends Statement /** * The parameters of this routine. * + * Used by `CREATE FUNCTION` and `CREATE PROCEDURE`. + * * @var ParamDefFragment[] */ public $parameters; + /** * The body of this function or procedure. * + * Used by `CREATE FUNCTION` and `CREATE PROCEDURE`. + * * @var Token[] */ public $body; /** - * Function called before the token is processed. - * - * Parsing the `CREATE` statement. - * * @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 before(Parser $parser, TokensList $list, Token $token) + public function parse(Parser $parser, TokensList $list) { - ++$list->idx; - $this->name = CreateDefFragment::parse($parser, $list); + ++$list->idx; // Skipping `CREATE`. + + // Parsing options. + $this->options = OptionsFragment::parse($parser, $list, static::$OPTIONS); + ++$list->idx; // Skipping last option. + + // Parsing the field name. + $this->name = FieldFragment::parse( + $parser, + $list, + array( + 'skipColumn' => true, + 'noBrackets' => true, + 'noAlias' => true, + ) + ); + ++$list->idx; // Skipping field. + if ($this->options->has('TABLE')) { - ++$list->idx; $this->fields = FieldDefFragment::parse($parser, $list); ++$list->idx; $this->entityOptions = OptionsFragment::parse( $parser, $list, - CreateDefFragment::$TABLE_OPTIONS + static::$TABLE_OPTIONS ); } elseif (($this->options->has('PROCEDURE')) || ($this->options->has('FUNCTION')) ) { - ++$list->idx; $this->parameters = ParamDefFragment::parse($parser, $list); if ($this->options->has('FUNCTION')) { $token = $list->getNextOfType(Token::TYPE_KEYWORD); @@ -147,7 +237,7 @@ class CreateStatement extends Statement $this->entityOptions = OptionsFragment::parse( $parser, $list, - CreateDefFragment::$FUNC_OPTIONS + static::$FUNC_OPTIONS ); ++$list->idx; $this->body = array(); @@ -160,6 +250,19 @@ class CreateStatement extends Statement break; } } + } else if ($this->options->has('VIEW')) { + $token = $list->getNext(); + + // Parsing columns list. + if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '(')) { + --$list->idx; // getNext() also goes forward one field. + $this->fields = ArrayFragment::parse($parser, $list); + ++$list->idx; // Skipping last token from the array. + $token = $list->getNext(); + } + + // Parsing the 'SELECT' statement. + $this->select = new SelectStatement($parser, $list); } } } diff --git a/src/TokensList.php b/src/TokensList.php index 6365be6..52a464e 100644 --- a/src/TokensList.php +++ b/src/TokensList.php @@ -52,14 +52,18 @@ class TokensList implements \ArrayAccess } /** - * Gets the next token. + * Gets the next token. Skips any irrelevant token (whitespaces and + * comments). * * @return Token */ public function getNext() { - if ($this->idx < $this->count) { - return $this->tokens[$this->idx++]; + for (; $this->idx < $this->count; ++$this->idx) { + if (($this->tokens[$this->idx]->type !== Token::TYPE_WHITESPACE) + && ($this->tokens[$this->idx]->type !== Token::TYPE_COMMENT)) { + return $this->tokens[$this->idx++]; + } } return null; } diff --git a/tests/Builder/StatementTest.php b/tests/Builder/StatementTest.php index fc646a8..0a65aac 100644 --- a/tests/Builder/StatementTest.php +++ b/tests/Builder/StatementTest.php @@ -32,14 +32,12 @@ class StatementTest extends TestCase $stmt->limit = new LimitKeyword(1, 10); - $builder = new Builder($stmt); - $this->assertEquals( 'SELECT DISTINCT sakila.film.film_id AS fid, COUNT(film_id) ' . 'FROM film, actor ' . 'WHERE film_id > 10 OR actor.age > 25 ' . 'LIMIT 10, 1 ', - $builder->query + $stmt->build() ); } } |