summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/Builder.php84
-rw-r--r--src/Fragments/CreateDefFragment.php125
-rw-r--r--src/Fragments/FieldFragment.php7
-rw-r--r--src/Parser.php6
-rw-r--r--src/Statement.php84
-rw-r--r--src/Statements/CreateStatement.php143
-rw-r--r--src/TokensList.php10
-rw-r--r--tests/Builder/StatementTest.php4
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()
);
}
}