Commit 394152e7 authored by jepso's avatar jepso

Updates to the new parser.

parent 71d1150e
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.phpdoctrine.org>.
*/
/**
* An LL(k) parser for the context-free grammar of Doctrine Query Language.
* Parses a DQL query, reports any errors in it, and generates the corresponding
* SQL.
*
* @package Doctrine
* @subpackage Query
* @author Janne Vanhala <jpvanhal@cc.hut.fi>
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.phpdoctrine.org
* @since 1.0
* @version $Revision$
*/
class Doctrine_Query_Parser
{
/**
......@@ -46,21 +79,41 @@ class Doctrine_Query_Parser
protected $_errorDistance = self::MIN_ERROR_DISTANCE;
/**
* A query printer object used to print a parse tree from the input string.
* A query printer object used to print a parse tree from the input string
* for debugging purposes.
*
* @var Doctrine_Query_Printer
*/
protected $_printer;
/**
* The connection object used by this query.
*
* @var Doctrine_Connection
*/
protected $_conn;
/**
* Creates a new query parser object.
*
* @param string $input query string to be parsed
* @param Doctrine_Connection The connection object the query will use.
*/
public function __construct($input)
public function __construct($input, Doctrine_Connection $conn = null)
{
$this->_scanner = new Doctrine_Query_Scanner($input);
$this->_printer = new Doctrine_Query_Printer(true);
if ($conn === null) {
$conn = Doctrine_Manager::getInstance()->getCurrentConnection();
}
$this->_conn = $conn;
}
public function getConnection()
{
return $this->_conn;
}
public function getProduction($name)
......@@ -90,26 +143,20 @@ class Doctrine_Query_Parser
}
if ($isMatch) {
//$this->_printer->println($this->lookahead['value']);
$this->_printer->println($this->lookahead['value']);
$this->lookahead = $this->_scanner->next();
$this->_errorDistance++;
} else {
$this->syntaxError();
$this->logError();
}
}
public function syntaxError()
{
$this->_error('Unexpected "' . $this->lookahead['value'] . '"');
}
public function semanticalError($message)
public function logError($message = '')
{
$this->_error($message);
}
if ($message === '') {
$message = 'Unexpected "' . $this->lookahead['value'] . '"';
}
protected function _error($message)
{
if ($this->_errorDistance >= self::MIN_ERROR_DISTANCE) {
$message .= 'at line ' . $this->lookahead['line']
. ', column ' . $this->lookahead['column'];
......
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.phpdoctrine.org>.
*/
/**
* An abstract base class that all query parser productions extend.
* An abstract base class for the productions of the Doctrine Query Language
* context-free grammar.
*
* @package Doctrine
* @subpackage Query
* @author Janne Vanhala <jpvanhal@cc.hut.fi>
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.phpdoctrine.org
* @since 1.0
* @version $Revision$
*/
abstract class Doctrine_Query_Production
{
......@@ -37,7 +66,6 @@ abstract class Doctrine_Query_Production
*/
public function __call($method, $args)
{
return $this->_parser->getProduction($method)->execute($args);
$this->_parser->getPrinter()->startProduction($name);
$retval = $this->_parser->getProduction($method)->execute($args);
$this->_parser->getPrinter()->endProduction();
......@@ -53,13 +81,4 @@ abstract class Doctrine_Query_Production
* @return mixed
*/
abstract public function execute(array $params = array());
protected function _isSubquery()
{
$lookahead = $this->_parser->lookahead;
$next = $this->_parser->getScanner()->peek();
return $lookahead['value'] === '(' && $next['type'] === Doctrine_Query_Token::T_SELECT;
}
}
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.phpdoctrine.org>.
*/
/**
* This class represents the AbstractSchemaName production in DQL grammar.
*
* <code>
* AbstractSchemaName = identifier
* </code>
*
* @package Doctrine
* @subpackage Query
* @author Janne Vanhala <jpvanhal@cc.hut.fi>
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.phpdoctrine.org
* @since 1.0
* @version $Revision$
*/
class Doctrine_Query_Production_AbstractSchemaName extends Doctrine_Query_Production
{
/**
* Executes the AbstractSchemaName production.
*
* <code>
* AbstractSchemaName = identifier
* </code>
*
* @param array $params This production does not take any parameters.
* @return Doctrine_Table|null the table object corresponding the identifier
* name
*/
public function execute(array $params = array())
{
$table = null;
$token = $this->_parser->lookahead;
if ($token['type'] === Doctrine_Query_Token::T_IDENTIFIER) {
$table = $this->_parser->getConnection()->getTable($token['value']);
if ($table === null) {
$this->_parser->logError('Table named "' . $name . '" does not exist.');
}
$this->_parser->match(Doctrine_Query_Token::T_IDENTIFIER);
} else {
$this->_parser->logError('Identifier expected');
}
return $table;
}
}
<?php
/**
* IdentificationVariable = identifier
*/
class Doctrine_Query_Production_IdentificationVariable extends Doctrine_Query_Production
{
public function execute(array $params = array())
{
$token = $this->_parser->lookahead;
$this->_parser->match(Doctrine_Query_Token::T_IDENTIFIER);
/*
if ( ! isValidIdentificationVariable($token['value'])) {
$this->error('"' . $name . '" is not a identification variable.');
}
*/
}
}
<?php
/**
* PathExpressionEndingWithAsterisk = {identifier "."} "*"
*/
class Doctrine_Query_Production_PathExpressionEndingWithAsterisk extends Doctrine_Query_Production
{
public function execute(array $params = array())
{
while ($this->_isNextToken(Doctrine_Query_Token::T_IDENTIFIER)) {
$this->_parser->match(Doctrine_Query_Token::T_IDENTIFIER);
$this->_parser->match('.');
}
$this->_parser->match('*');
}
}
......@@ -18,7 +18,7 @@ class Doctrine_Query_Production_QueryLanguage extends Doctrine_Query_Production
$this->DeleteStatement();
break;
default:
$this->_parser->syntaxError();
$this->_parser->logError();
}
}
}
<?php
/**
* RangeVariableDeclaration = PathExpression [["AS" ] identifier]
* RangeVariableDeclaration = AbstractSchemaName ["AS"] IdentificationVariable
*/
class Doctrine_Query_Production_RangeVariableDeclaration extends Doctrine_Query_Production
{
public function execute(array $params = array())
{
$this->PathExpression();
$this->AbstractSchemaName();
if ($this->_isNextToken(Doctrine_Query_Token::T_AS)) {
$this->_parser->match(Doctrine_Query_Token::T_AS);
$this->_parser->match(Doctrine_Query_Token::T_IDENTIFIER);
} elseif ($this->_isNextToken(Doctrine_Query_Token::T_IDENTIFIER)) {
$this->_parser->match(Doctrine_Query_Token::T_IDENTIFIER);
}
$this->IdentificationVariable();
}
}
<?php
/**
* SelectExpression = (Expression | "(" Subselect ")" ) [["AS"] identifier]
* SelectExpression = (PathExpressionEndingWithAsterisk | Expression | "(" Subselect ")")
* [["AS"] IdentificationVariable]
*/
class Doctrine_Query_Production_SelectExpression extends Doctrine_Query_Production
{
private function _isPathExpressionEndingWithAsterisk()
{
$token = $this->_parser->lookahead;
$this->_parser->getScanner()->resetPeek();
while (($token['type'] === Doctrine_Query_Token::T_IDENTIFIER) || ($token['value'] === '.')) {
$token = $this->_parser->getScanner()->peek();
}
return $token['value'] === '*';
}
private function _isSubquery()
{
$lookahead = $this->_parser->lookahead;
$next = $this->_parser->getScanner()->peek();
return $lookahead['value'] === '(' && $next['type'] === Doctrine_Query_Token::T_SELECT;
}
public function execute(array $params = array())
{
if ($this->_isSubquery()) {
if ($this->_isPathExpressionEndingWithAsterisk()) {
$this->PathExpressionEndingWithAsterisk();
} elseif ($this->_isSubquery()) {
$this->_parser->match('(');
$this->Subselect();
$this->_parser->match(')');
......@@ -18,7 +41,7 @@ class Doctrine_Query_Production_SelectExpression extends Doctrine_Query_Producti
$this->_parser->match(Doctrine_Query_Token::T_AS);
$this->_parser->match(Doctrine_Query_Token::T_IDENTIFIER);
} elseif ($this->_isNextToken(Doctrine_Query_Token::T_IDENTIFIER)) {
$this->_parser->match(Doctrine_Query_Token::T_IDENTIFIER);
$this->IdentificationVariable();
}
}
}
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.phpdoctrine.org>.
*/
/**
* ...
*
* @package Doctrine
* @subpackage Query
* @author Janne Vanhala <jpvanhal@cc.hut.fi>
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.phpdoctrine.org
* @since 1.0
* @version $Revision$
*/
class Doctrine_Query_Scanner
{
/**
......
......@@ -50,14 +50,6 @@ final class Doctrine_Query_Token
const T_SQRT = 141;
const T_MOD = 142;
const T_SIZE = 143;
const T_CURRENT_DATE = 144;
const T_CURRENT_TIMESTAMP = 145;
const T_CURRENT_TIME = 146;
const T_SUBSTRING = 147;
const T_CONCAT = 148;
const T_TRIM = 149;
const T_LOWER = 150;
const T_UPPER = 151;
private function __construct() {}
}
/* Context-free grammar for Doctrine Query Language
*
* Document syntax:
* - non-terminals begin with an upper case character
* - terminals begin with a lower case character
* - parentheses (...) are used for grouping
* - square brackets [...] are used for defining an optional part, eg. zero or
* one time time
* - curly brackets {...} are used for repetion, eg. zero or more times
* - double quotation marks "..." define a terminal string
* - a vertical bar represents an alternative
*/
QueryLanguage = SelectStatement | UpdateStatement | DeleteStatement
SelectStatement = [SelectClause] FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] [LimitClause]
......@@ -20,11 +33,11 @@ OrderByItem = PathExpression ["ASC" | "DESC"]
GroupByItem = PathExpression
UpdateItem = PathExpression "=" (Expression | "NULL")
IdentificationVariableDeclaration = RangeVariableDeclaration {Join}
RangeVariableDeclaration = PathExpression [["AS" ] identifier<identification-variable>]
IdentificationVariableDeclaration = RangeVariableDeclaration {Join}
RangeVariableDeclaration = AbstractSchemaName ["AS"] IdentificationVariable
Join = ["LEFT" | "INNER"] "JOIN" PathExpression "AS" identifier [["ON" | "WITH"] ConditionalExpression] IndexBy
IndexBy = "INDEXBY" identifier
Join = ["LEFT" | "INNER"] "JOIN" PathExpression ["AS"] IdentificationVariable [("ON" | "WITH") ConditionalExpression] [IndexBy]
IndexBy = "INDEXBY" PathExpression
ConditionalExpression = ConditionalTerm {"OR" ConditionalTerm}
ConditionalTerm = ConditionalFactor {"AND" ConditionalFactor}
......@@ -34,15 +47,16 @@ SimpleConditionalExpression
= Expression (ComparisonExpression | BetweenExpression | LikeExpression
| InExpression | NullComparisonExpression) | ExistsExpression
Atom = string-literal | numeric-constant | input-parameter
Atom = string_literal | numeric_constant | input_parameter
Expression = Expression {("+" | "-" | "*" | "/") Expression}
Expression = ("+" | "-") Expression
Expression = "(" Expression ")"
Expression = PathExpression | Atom | | Function | AggregateExpression
Expression = Term {("+" | "-") Term}
Term = Factor {("*" | "/") Factor}
Factor = [("+" | "-")] Primary
Primary = PathExpression | Atom | "(" Expression ")" | Function | AggregateExpression
SelectExpression = (Expression | "(" Subselect ")" ) [["AS"] identifier]
SelectExpression = (PathExpressionEndingWithAsterisk | Expression | "(" Subselect ")" ) [["AS"] IdentificationVariable]
PathExpression = identifier {"." identifier}
PathExpressionEndingWithAsterisk = {identifier "."} "*"
AggregateExpression = ("AVG" | "MAX" | "MIN" | "SUM") "(" ["DISTINCT"] Expression ")"
| "COUNT" "(" ["DISTINCT"] (Expression | "*") ")"
......@@ -51,26 +65,8 @@ QuantifiedExpression = ("ALL" | "ANY" | "SOME") "(" Subselect ")"
BetweenExpression = ["NOT"] "BETWEEN" Expression "AND" Expression
ComparisonExpression = ComparisonOperator ( QuantifiedExpression | Expression | "(" Subselect ")" )
InExpression = ["NOT"] "IN" "(" (Atom {"," Atom} | Subselect) ")"
LikeExpression = ["NOT"] "LIKE" Expression ["ESCAPE" escape_character]
LikeExpression = ["NOT"] "LIKE" Expression ["ESCAPE" string_literal]
NullComparisonExpression = "IS" ["NOT"] "NULL"
ExistsExpression = ["NOT"] "EXISTS" "(" Subselect ")"
Function =
"CURRENT_DATE" |
"CURRENT_TIME" |
"CURRENT_TIMESTAMP" |
"LENGTH" "(" Expression ")" |
"LOCATE" "(" Expression "," Expression ["," Expression] ")" |
"ABS" "(" Expression ")" |
"SQRT" "(" Expression ")" |
"MOD" "(" Expression "," Expression ")" |
"SIZE" "(" Expression ")" |
"CONCAT" "(" Expression "," Expression ")" |
"SUBSTRING" "(" Expression "," Expression "," "Expression" ")" |
"TRIM" "(" [[TrimSpecification] [trim_character] "FROM"] string_primary ")" |
"LOWER" "(" string_primary ")" |
"UPPER" "(" string_primary ")" |
identifier "(" [Expression {"," Expression}]")" // Custom function
TrimSpecification = "LEADING" | "TRAILING" | "BOTH"
Function = identifier "(" [Expression {"," Expression}] ")"
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment