Commit 58a15786 authored by guilhermeblanco's avatar guilhermeblanco

[2.0] Finished reorganization of grammar rules in DQL parser source code....

[2.0] Finished reorganization of grammar rules in DQL parser source code. Renamed some private methods.
parent 1299e838
...@@ -110,11 +110,11 @@ class Parser ...@@ -110,11 +110,11 @@ class Parser
private $_queryComponents = array(); private $_queryComponents = array();
/** /**
* Sql tree walker * Tree walker chain
* *
* @var SqlTreeWalker * @var TreeWalker
*/ */
private $_sqlTreeWalker; private $_treeWalker;
/** /**
* Creates a new query parser object. * Creates a new query parser object.
...@@ -129,6 +129,79 @@ class Parser ...@@ -129,6 +129,79 @@ class Parser
$this->_parserResult = new ParserResult(); $this->_parserResult = new ParserResult();
} }
/**
* Sets the custom tree walker.
*
* @param TreeWalker $treeWalker
*/
public function setTreeWalker($treeWalker)
{
$this->_treeWalker = $treeWalker;
}
/**
* Gets the lexer used by the parser.
*
* @return Doctrine\ORM\Query\Lexer
*/
public function getLexer()
{
return $this->_lexer;
}
/**
* Gets the ParserResult that is being filled with information during parsing.
*
* @return Doctrine\ORM\Query\ParserResult
*/
public function getParserResult()
{
return $this->_parserResult;
}
/**
* Gets the EntityManager used by the parser.
*
* @return EntityManager
*/
public function getEntityManager()
{
return $this->_em;
}
/**
* Registers a custom function that returns strings.
*
* @param string $name The function name.
* @param string $class The class name of the function implementation.
*/
public static function registerStringFunction($name, $class)
{
self::$_STRING_FUNCTIONS[$name] = $class;
}
/**
* Registers a custom function that returns numerics.
*
* @param string $name The function name.
* @param string $class The class name of the function implementation.
*/
public static function registerNumericFunction($name, $class)
{
self::$_NUMERIC_FUNCTIONS[$name] = $class;
}
/**
* Registers a custom function that returns date/time values.
*
* @param string $name The function name.
* @param string $class The class name of the function implementation.
*/
public static function registerDatetimeFunction($name, $class)
{
self::$_DATETIME_FUNCTIONS[$name] = $class;
}
/** /**
* Attempts to match the given token with the current lookahead token. * Attempts to match the given token with the current lookahead token.
* *
...@@ -183,57 +256,17 @@ class Parser ...@@ -183,57 +256,17 @@ class Parser
$this->syntaxError('end of string'); $this->syntaxError('end of string');
} }
// Create SqlWalker who creates the SQL from the AST // Create TreeWalker who creates the SQL from the AST
$sqlWalker = $this->_sqlTreeWalker ?: new SqlWalker( $treeWalker = $this->_treeWalker ?: new SqlWalker(
$this->_query, $this->_parserResult, $this->_queryComponents $this->_query, $this->_parserResult, $this->_queryComponents
); );
// Assign an SQL executor to the parser result // Assign an SQL executor to the parser result
$this->_parserResult->setSqlExecutor($sqlWalker->getExecutor($AST)); $this->_parserResult->setSqlExecutor($treeWalker->getExecutor($AST));
return $this->_parserResult;
}
/**
* Sets the custom tree walker.
*
* @param SqlTreeWalker $sqlTreeWalker
*/
public function setSqlTreeWalker($sqlTreeWalker)
{
$this->_sqlTreeWalker = $sqlTreeWalker;
}
/**
* Gets the lexer used by the parser.
*
* @return Doctrine\ORM\Query\Lexer
*/
public function getLexer()
{
return $this->_lexer;
}
/**
* Gets the ParserResult that is being filled with information during parsing.
*
* @return Doctrine\ORM\Query\ParserResult
*/
public function getParserResult()
{
return $this->_parserResult; return $this->_parserResult;
} }
/**
* Gets the EntityManager used by the parser.
*
* @return EntityManager
*/
public function getEntityManager()
{
return $this->_em;
}
/** /**
* Generates a new syntax error. * Generates a new syntax error.
* *
...@@ -320,6 +353,39 @@ class Parser ...@@ -320,6 +353,39 @@ class Parser
return ($peek['value'] === '(' && $nextpeek['type'] !== Lexer::T_SELECT); return ($peek['value'] === '(' && $nextpeek['type'] !== Lexer::T_SELECT);
} }
/**
* Checks whether the function with the given name is a string function
* (a function that returns strings).
*
* @return boolean TRUE if the token type is a string function, FALSE otherwise.
*/
private function _isStringFunction($funcName)
{
return isset(self::$_STRING_FUNCTIONS[strtolower($funcName)]);
}
/**
* Checks whether the function with the given name is a numeric function
* (a function that returns numerics).
*
* @return boolean TRUE if the token type is a numeric function, FALSE otherwise.
*/
private function _isNumericFunction($funcName)
{
return isset(self::$_NUMERIC_FUNCTIONS[strtolower($funcName)]);
}
/**
* Checks whether the function with the given name is a datetime function
* (a function that returns date/time values).
*
* @return boolean TRUE if the token type is a datetime function, FALSE otherwise.
*/
private function _isDatetimeFunction($funcName)
{
return isset(self::$_DATETIME_FUNCTIONS[strtolower($funcName)]);
}
/** /**
* Checks whether the given token type indicates an aggregate function. * Checks whether the given token type indicates an aggregate function.
* *
...@@ -332,6 +398,19 @@ class Parser ...@@ -332,6 +398,19 @@ class Parser
$tokenType == Lexer::T_COUNT; $tokenType == Lexer::T_COUNT;
} }
/**
* Checks whether the current lookahead token of the lexer has the type
* T_ALL, T_ANY or T_SOME.
*
* @return boolean
*/
private function _isNextAllAnySome()
{
return $this->_lexer->lookahead['type'] === Lexer::T_ALL ||
$this->_lexer->lookahead['type'] === Lexer::T_ANY ||
$this->_lexer->lookahead['type'] === Lexer::T_SOME;
}
/** /**
* Checks whether the next 2 tokens start a subselect. * Checks whether the next 2 tokens start a subselect.
* *
...@@ -779,6 +858,51 @@ class Parser ...@@ -779,6 +858,51 @@ class Parser
} }
/**
* SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}
*/
public function SelectClause()
{
$isDistinct = false;
$this->match(Lexer::T_SELECT);
// Check for DISTINCT
if ($this->_lexer->isNextToken(Lexer::T_DISTINCT)) {
$this->match(Lexer::T_DISTINCT);
$isDistinct = true;
}
// Process SelectExpressions (1..N)
$selectExpressions = array();
$selectExpressions[] = $this->SelectExpression();
while ($this->_lexer->isNextToken(',')) {
$this->match(',');
$selectExpressions[] = $this->SelectExpression();
}
return new AST\SelectClause($selectExpressions, $isDistinct);
}
/**
* SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression
*/
public function SimpleSelectClause()
{
$distinct = false;
$this->match(Lexer::T_SELECT);
if ($this->_lexer->isNextToken(Lexer::T_DISTINCT)) {
$this->match(Lexer::T_DISTINCT);
$distinct = true;
}
$simpleSelectClause = new AST\SimpleSelectClause($this->SimpleSelectExpression());
$simpleSelectClause->setDistinct($distinct);
return $simpleSelectClause;
}
/** /**
* UpdateClause ::= "UPDATE" AbstractSchemaName [["AS"] AliasIdentificationVariable] "SET" UpdateItem {"," UpdateItem}* * UpdateClause ::= "UPDATE" AbstractSchemaName [["AS"] AliasIdentificationVariable] "SET" UpdateItem {"," UpdateItem}*
*/ */
...@@ -825,62 +949,6 @@ class Parser ...@@ -825,62 +949,6 @@ class Parser
return $updateClause; return $updateClause;
} }
/**
* UpdateItem ::= [IdentificationVariable "."] {StateField | SingleValuedAssociationField} "=" NewValue
*/
public function UpdateItem()
{
$peek = $this->_lexer->glimpse();
$identVariable = null;
if ($peek['value'] == '.') {
$this->match(Lexer::T_IDENTIFIER);
$identVariable = $this->_lexer->token['value'];
$this->match('.');
} else {
throw QueryException::missingAliasQualifier();
}
$this->match(Lexer::T_IDENTIFIER);
$field = $this->_lexer->token['value'];
$this->match('=');
$newValue = $this->NewValue();
$updateItem = new AST\UpdateItem($field, $newValue);
$updateItem->setIdentificationVariable($identVariable);
return $updateItem;
}
/**
* NewValue ::= SimpleArithmeticExpression | StringPrimary | DatetimePrimary | BooleanPrimary |
* EnumPrimary | SimpleEntityExpression | "NULL"
*
* NOTE: Since it is not possible to correctly recognize individual types, here is the full
* grammar that needs to be supported:
*
* NewValue ::= SimpleArithmeticExpression | "NULL"
*
* SimpleArithmeticExpression covers all *Primary grammar rules and also SimplEntityExpression
*
* @todo Find why removal of InputParameter check causes ClassTableInheritanceTest to fail with
* wrong parameter count (Should be processed in Literal, part of SimpleArithmeticExpression)
*/
public function NewValue()
{
if ($this->_lexer->isNextToken(Lexer::T_NULL)) {
$this->match(Lexer::T_NULL);
return null;
} else if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
$this->match(Lexer::T_INPUT_PARAMETER);
return new AST\InputParameter($this->_lexer->token['value']);
}
return $this->SimpleArithmeticExpression();
}
/** /**
* DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName [["AS"] AliasIdentificationVariable] * DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName [["AS"] AliasIdentificationVariable]
*/ */
...@@ -915,98 +983,228 @@ class Parser ...@@ -915,98 +983,228 @@ class Parser
} }
/** /**
* SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression} * FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}*
*/ */
public function SelectClause() public function FromClause()
{ {
$isDistinct = false; $this->match(Lexer::T_FROM);
$this->match(Lexer::T_SELECT); $identificationVariableDeclarations = array();
$identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
// Check for DISTINCT
if ($this->_lexer->isNextToken(Lexer::T_DISTINCT)) { while ($this->_lexer->isNextToken(',')) {
$this->match(Lexer::T_DISTINCT); $this->match(',');
$isDistinct = true; $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
} }
// Process SelectExpressions (1..N) return new AST\FromClause($identificationVariableDeclarations);
$selectExpressions = array(); }
$selectExpressions[] = $this->SelectExpression();
/**
* SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}*
*/
public function SubselectFromClause()
{
$this->match(Lexer::T_FROM);
$identificationVariables = array();
$identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
while ($this->_lexer->isNextToken(',')) { while ($this->_lexer->isNextToken(',')) {
$this->match(','); $this->match(',');
$selectExpressions[] = $this->SelectExpression(); $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
} }
return new AST\SelectClause($selectExpressions, $isDistinct); return new AST\SubselectFromClause($identificationVariables);
} }
/** /**
* FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}* * WhereClause ::= "WHERE" ConditionalExpression
*/ */
public function FromClause() public function WhereClause()
{ {
$this->match(Lexer::T_FROM); $this->match(Lexer::T_WHERE);
$identificationVariableDeclarations = array();
$identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration(); return new AST\WhereClause($this->ConditionalExpression());
}
/**
* HavingClause ::= "HAVING" ConditionalExpression
*/
public function HavingClause()
{
$this->match(Lexer::T_HAVING);
return new AST\HavingClause($this->ConditionalExpression());
}
/**
* GroupByClause ::= "GROUP" "BY" GroupByItem {"," GroupByItem}*
*/
public function GroupByClause()
{
$this->match(Lexer::T_GROUP);
$this->match(Lexer::T_BY);
$groupByItems = array($this->GroupByItem());
while ($this->_lexer->isNextToken(',')) { while ($this->_lexer->isNextToken(',')) {
$this->match(','); $this->match(',');
$identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration(); $groupByItems[] = $this->GroupByItem();
} }
return new AST\FromClause($identificationVariableDeclarations); return new AST\GroupByClause($groupByItems);
}
/**
* OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}*
*/
public function OrderByClause()
{
$this->match(Lexer::T_ORDER);
$this->match(Lexer::T_BY);
$orderByItems = array();
$orderByItems[] = $this->OrderByItem();
while ($this->_lexer->isNextToken(',')) {
$this->match(',');
$orderByItems[] = $this->OrderByItem();
}
return new AST\OrderByClause($orderByItems);
} }
/** /**
* SelectExpression ::= * Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
* IdentificationVariable | StateFieldPathExpression |
* (AggregateExpression | "(" Subselect ")" | Function) [["AS"] ResultVariable]
*/ */
public function SelectExpression() public function Subselect()
{
$this->_beginDeferredPathExpressionStack();
$subselect = new AST\Subselect($this->SimpleSelectClause(), $this->SubselectFromClause());
$this->_processDeferredPathExpressionStack();
$subselect->setWhereClause(
$this->_lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null
);
$subselect->setGroupByClause(
$this->_lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null
);
$subselect->setHavingClause(
$this->_lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null
);
$subselect->setOrderByClause(
$this->_lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null
);
return $subselect;
}
/**
* UpdateItem ::= [IdentificationVariable "."] {StateField | SingleValuedAssociationField} "=" NewValue
*/
public function UpdateItem()
{ {
$expression = null;
$fieldAliasIdentificationVariable = null;
$peek = $this->_lexer->glimpse(); $peek = $this->_lexer->glimpse();
$identVariable = null;
// First we recognize for an IdentificationVariable (DQL class alias) if ($peek['value'] == '.') {
if ($peek['value'] != '.' && $peek['value'] != '(' && $this->_lexer->lookahead['type'] === Lexer::T_IDENTIFIER) { $this->match(Lexer::T_IDENTIFIER);
$expression = $this->IdentificationVariable(); $identVariable = $this->_lexer->token['value'];
} else if (($isFunction = $this->_isFunction()) !== false || $this->_isSubselect()) { $this->match('.');
if ($isFunction) { } else {
if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) { throw QueryException::missingAliasQualifier();
$expression = $this->AggregateExpression(); }
} else {
$expression = $this->_Function();
}
} else {
$this->match('(');
$expression = $this->Subselect();
$this->match(')');
}
if ($this->_lexer->isNextToken(Lexer::T_AS)) { $this->match(Lexer::T_IDENTIFIER);
$this->match(Lexer::T_AS); $field = $this->_lexer->token['value'];
}
$this->match('=');
$newValue = $this->NewValue();
if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) { $updateItem = new AST\UpdateItem($field, $newValue);
$fieldAliasIdentificationVariable = $this->ResultVariable(); $updateItem->setIdentificationVariable($identVariable);
}
return $updateItem;
}
/**
* GroupByItem ::= IdentificationVariable | SingleValuedPathExpression
*
* @todo Finish this implementation
*/
public function GroupByItem()
{
return $this->SingleValuedPathExpression();
}
/**
* OrderByItem ::= (ResultVariable | StateFieldPathExpression) ["ASC" | "DESC"]
*
* @todo Support general SingleValuedPathExpression instead of only StateFieldPathExpression.
*/
public function OrderByItem()
{
// We need to check if we are in a ResultVariable or StateFieldPathExpression
$glimpse = $this->_lexer->glimpse();
if ($glimpse['value'] != '.') {
$expr = $this->ResultVariable();
// @todo Check if ResultVariable is defined somewhere
} else { } else {
// Deny hydration of partial objects if doctrine.forcePartialLoad query hint not defined $expr = $this->StateFieldPathExpression();
if ( }
$this->_query->getHydrationMode() == Query::HYDRATE_OBJECT &&
! $this->_em->getConfiguration()->getAllowPartialObjects() && $item = new AST\OrderByItem($expr);
! $this->_query->getHint('doctrine.forcePartialLoad')
) {
throw DoctrineException::partialObjectsAreDangerous();
}
$expression = $this->StateFieldPathExpression(); if ($this->_lexer->isNextToken(Lexer::T_ASC)) {
$this->match(Lexer::T_ASC);
$item->setAsc(true);
return $item;
}
if ($this->_lexer->isNextToken(Lexer::T_DESC)) {
$this->match(Lexer::T_DESC);
} }
$item->setDesc(true);
return $item;
}
return new AST\SelectExpression($expression, $fieldAliasIdentificationVariable); /**
* NewValue ::= SimpleArithmeticExpression | StringPrimary | DatetimePrimary | BooleanPrimary |
* EnumPrimary | SimpleEntityExpression | "NULL"
*
* NOTE: Since it is not possible to correctly recognize individual types, here is the full
* grammar that needs to be supported:
*
* NewValue ::= SimpleArithmeticExpression | "NULL"
*
* SimpleArithmeticExpression covers all *Primary grammar rules and also SimplEntityExpression
*
* @todo Find why removal of InputParameter check causes ClassTableInheritanceTest to fail with
* wrong parameter count (Should be processed in Literal, part of SimpleArithmeticExpression)
*/
public function NewValue()
{
if ($this->_lexer->isNextToken(Lexer::T_NULL)) {
$this->match(Lexer::T_NULL);
return null;
} else if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
$this->match(Lexer::T_INPUT_PARAMETER);
return new AST\InputParameter($this->_lexer->token['value']);
}
return $this->SimpleArithmeticExpression();
} }
/** /**
* IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration}* * IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration}*
*/ */
...@@ -1029,6 +1227,38 @@ class Parser ...@@ -1029,6 +1227,38 @@ class Parser
); );
} }
/**
* SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable)
*/
public function SubselectIdentificationVariableDeclaration()
{
$peek = $this->_lexer->glimpse();
if ($peek['value'] == '.') {
$subselectIdentificationVarDecl = new AST\SubselectIdentificationVariableDeclaration;
$subselectIdentificationVarDecl->setAssociationPathExpression($this->AssociationPathExpression());
$this->match(Lexer::T_AS);
$this->match(Lexer::T_IDENTIFIER);
$subselectIdentificationVarDecl->setAliasIdentificationVariable($this->_lexer->token['value']);
return $subselectIdentificationVarDecl;
}
return $this->IdentificationVariableDeclaration();
}
/**
* JoinVariableDeclaration ::= Join [IndexBy]
*/
public function JoinVariableDeclaration()
{
$join = $this->Join();
$indexBy = $this->_lexer->isNextToken(Lexer::T_INDEX)
? $this->IndexBy() : null;
return new AST\JoinVariableDeclaration($join, $indexBy);
}
/** /**
* RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable * RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
*/ */
...@@ -1057,18 +1287,6 @@ class Parser ...@@ -1057,18 +1287,6 @@ class Parser
); );
} }
/**
* JoinVariableDeclaration ::= Join [IndexBy]
*/
public function JoinVariableDeclaration()
{
$join = $this->Join();
$indexBy = $this->_lexer->isNextToken(Lexer::T_INDEX)
? $this->IndexBy() : null;
return new AST\JoinVariableDeclaration($join, $indexBy);
}
/** /**
* Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinPathExpression * Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinPathExpression
* ["AS"] AliasIdentificationVariable [("ON" | "WITH") ConditionalExpression] * ["AS"] AliasIdentificationVariable [("ON" | "WITH") ConditionalExpression]
...@@ -1156,205 +1374,89 @@ class Parser ...@@ -1156,205 +1374,89 @@ class Parser
return $pathExp; return $pathExp;
} }
/**
* EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression
*/
public function EntityExpression()
{
$glimpse = $this->_lexer->glimpse();
if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER) && $glimpse['value'] === '.') {
return $this->SingleValuedAssociationPathExpression();
}
return $this->SimpleEntityExpression();
}
/** /**
* SimpleEntityExpression ::= IdentificationVariable | InputParameter * SelectExpression ::=
*/ * IdentificationVariable | StateFieldPathExpression |
public function SimpleEntityExpression() * (AggregateExpression | "(" Subselect ")" | FunctionDeclaration) [["AS"] ResultVariable]
{
if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
$this->match(Lexer::T_INPUT_PARAMETER);
return new AST\InputParameter($this->_lexer->token['value']);
}
return $this->IdentificationVariable();
}
/**
* NullComparisonExpression ::= (SingleValuedPathExpression | InputParameter) "IS" ["NOT"] "NULL"
*/
public function NullComparisonExpression()
{
if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
$this->match(Lexer::T_INPUT_PARAMETER);
$expr = new AST\InputParameter($this->_lexer->token['value']);
} else {
$expr = $this->SingleValuedPathExpression();
}
$nullCompExpr = new AST\NullComparisonExpression($expr);
$this->match(Lexer::T_IS);
if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
$this->match(Lexer::T_NOT);
$nullCompExpr->setNot(true);
}
$this->match(Lexer::T_NULL);
return $nullCompExpr;
}
/**
* AggregateExpression ::=
* ("AVG" | "MAX" | "MIN" | "SUM") "(" ["DISTINCT"] StateFieldPathExpression ")" |
* "COUNT" "(" ["DISTINCT"] (IdentificationVariable | SingleValuedPathExpression) ")"
*/ */
public function AggregateExpression() public function SelectExpression()
{ {
$isDistinct = false; $expression = null;
$functionName = ''; $fieldAliasIdentificationVariable = null;
$peek = $this->_lexer->glimpse();
if ($this->_lexer->isNextToken(Lexer::T_COUNT)) {
$this->match(Lexer::T_COUNT);
$functionName = $this->_lexer->token['value'];
$this->match('(');
if ($this->_lexer->isNextToken(Lexer::T_DISTINCT)) {
$this->match(Lexer::T_DISTINCT);
$isDistinct = true;
}
$pathExp = $this->SingleValuedPathExpression(); // First we recognize for an IdentificationVariable (DQL class alias)
$this->match(')'); if ($peek['value'] != '.' && $peek['value'] != '(' && $this->_lexer->lookahead['type'] === Lexer::T_IDENTIFIER) {
} else { $expression = $this->IdentificationVariable();
if ($this->_lexer->isNextToken(Lexer::T_AVG)) { } else if (($isFunction = $this->_isFunction()) !== false || $this->_isSubselect()) {
$this->match(Lexer::T_AVG); if ($isFunction) {
} else if ($this->_lexer->isNextToken(Lexer::T_MAX)) { if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
$this->match(Lexer::T_MAX); $expression = $this->AggregateExpression();
} else if ($this->_lexer->isNextToken(Lexer::T_MIN)) { } else {
$this->match(Lexer::T_MIN); $expression = $this->FunctionDeclaration();
} else if ($this->_lexer->isNextToken(Lexer::T_SUM)) { }
$this->match(Lexer::T_SUM);
} else { } else {
$this->syntaxError('One of: MAX, MIN, AVG, SUM, COUNT'); $this->match('(');
$expression = $this->Subselect();
$this->match(')');
} }
$functionName = $this->_lexer->token['value']; if ($this->_lexer->isNextToken(Lexer::T_AS)) {
$this->match('('); $this->match(Lexer::T_AS);
$pathExp = $this->StateFieldPathExpression(); }
$this->match(')');
}
return new AST\AggregateExpression($functionName, $pathExp, $isDistinct);
}
/**
* GroupByClause ::= "GROUP" "BY" GroupByItem {"," GroupByItem}*
*/
public function GroupByClause()
{
$this->match(Lexer::T_GROUP);
$this->match(Lexer::T_BY);
$groupByItems = array($this->GroupByItem());
while ($this->_lexer->isNextToken(',')) {
$this->match(',');
$groupByItems[] = $this->GroupByItem();
}
return new AST\GroupByClause($groupByItems);
}
/**
* GroupByItem ::= IdentificationVariable | SingleValuedPathExpression
*
* @todo Finish this implementation
*/
public function GroupByItem()
{
return $this->SingleValuedPathExpression();
}
/**
* HavingClause ::= "HAVING" ConditionalExpression
*/
public function HavingClause()
{
$this->match(Lexer::T_HAVING);
return new AST\HavingClause($this->ConditionalExpression());
}
/**
* OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}*
*/
public function OrderByClause()
{
$this->match(Lexer::T_ORDER);
$this->match(Lexer::T_BY);
$orderByItems = array(); if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) {
$orderByItems[] = $this->OrderByItem(); $fieldAliasIdentificationVariable = $this->ResultVariable();
}
} else {
// Deny hydration of partial objects if doctrine.forcePartialLoad query hint not defined
if (
$this->_query->getHydrationMode() == Query::HYDRATE_OBJECT &&
! $this->_em->getConfiguration()->getAllowPartialObjects() &&
! $this->_query->getHint('doctrine.forcePartialLoad')
) {
throw DoctrineException::partialObjectsAreDangerous();
}
while ($this->_lexer->isNextToken(',')) { $expression = $this->StateFieldPathExpression();
$this->match(',');
$orderByItems[] = $this->OrderByItem();
} }
return new AST\OrderByClause($orderByItems); return new AST\SelectExpression($expression, $fieldAliasIdentificationVariable);
} }
/** /**
* OrderByItem ::= (ResultVariable | StateFieldPathExpression) ["ASC" | "DESC"] * SimpleSelectExpression ::= StateFieldPathExpression | IdentificationVariable | (AggregateExpression [["AS"] ResultVariable])
*
* @todo Support general SingleValuedPathExpression instead of only StateFieldPathExpression.
*/ */
public function OrderByItem() public function SimpleSelectExpression()
{ {
// We need to check if we are in a ResultVariable or StateFieldPathExpression if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) {
$glimpse = $this->_lexer->glimpse(); // SingleValuedPathExpression | IdentificationVariable
$peek = $this->_lexer->glimpse();
if ($glimpse['value'] != '.') {
$expr = $this->ResultVariable(); if ($peek['value'] == '.') {
return new AST\SimpleSelectExpression($this->StateFieldPathExpression());
// @todo Check if ResultVariable is defined somewhere }
$this->match($this->_lexer->lookahead['value']);
return new AST\SimpleSelectExpression($this->_lexer->token['value']);
} else { } else {
$expr = $this->StateFieldPathExpression(); $expr = new AST\SimpleSelectExpression($this->AggregateExpression());
}
$item = new AST\OrderByItem($expr);
if ($this->_lexer->isNextToken(Lexer::T_ASC)) { if ($this->_lexer->isNextToken(Lexer::T_AS)) {
$this->match(Lexer::T_ASC); $this->match(Lexer::T_AS);
$item->setAsc(true); }
return $item;
}
if ($this->_lexer->isNextToken(Lexer::T_DESC)) {
$this->match(Lexer::T_DESC);
}
$item->setDesc(true);
return $item;
}
/** if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) {
* WhereClause ::= "WHERE" ConditionalExpression $expr->setFieldIdentificationVariable($this->ResultVariable());
*/ }
public function WhereClause()
{
$this->match(Lexer::T_WHERE);
return new AST\WhereClause($this->ConditionalExpression()); return $expr;
}
} }
/** /**
* ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}* * ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}*
*/ */
...@@ -1450,6 +1552,8 @@ class Parser ...@@ -1450,6 +1552,8 @@ class Parser
* ComparisonExpression | BetweenExpression | LikeExpression | * ComparisonExpression | BetweenExpression | LikeExpression |
* InExpression | NullComparisonExpression | ExistsExpression | * InExpression | NullComparisonExpression | ExistsExpression |
* EmptyCollectionComparisonExpression | CollectionMemberExpression * EmptyCollectionComparisonExpression | CollectionMemberExpression
*
* @todo Missing EmptyCollectionComparisonExpression implementation
*/ */
public function SimpleConditionalExpression() public function SimpleConditionalExpression()
{ {
...@@ -1512,6 +1616,7 @@ class Parser ...@@ -1512,6 +1616,7 @@ class Parser
} }
} }
/** /**
* CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression * CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression
* *
...@@ -1542,99 +1647,33 @@ class Parser ...@@ -1542,99 +1647,33 @@ class Parser
); );
} }
/** /**
* ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression ) * Literal ::= string | char | integer | float | boolean | InputParameter
* *
* @return AST\ComparisonExpression * @todo Rip out InputParameter. Thats not a literal.
* @todo Semantical checks whether $leftExpr $operator and $rightExpr are compatible.
*/ */
public function ComparisonExpression() public function Literal()
{ {
$peek = $this->_lexer->glimpse(); switch ($this->_lexer->lookahead['type']) {
case Lexer::T_INPUT_PARAMETER:
$leftExpr = $this->ArithmeticExpression(); $this->match($this->_lexer->lookahead['value']);
$operator = $this->ComparisonOperator();
if ($this->_isNextAllAnySome()) {
$rightExpr = $this->QuantifiedExpression();
} else {
$rightExpr = $this->ArithmeticExpression();
}
return new AST\ComparisonExpression($leftExpr, $operator, $rightExpr); return new AST\InputParameter($this->_lexer->token['value']);
}
/** case Lexer::T_STRING:
* Checks whether the current lookahead token of the lexer has the type case Lexer::T_INTEGER:
* T_ALL, T_ANY or T_SOME. case Lexer::T_FLOAT:
* $this->match($this->_lexer->lookahead['value']);
* @return boolean
*/
private function _isNextAllAnySome()
{
return $this->_lexer->lookahead['type'] === Lexer::T_ALL ||
$this->_lexer->lookahead['type'] === Lexer::T_ANY ||
$this->_lexer->lookahead['type'] === Lexer::T_SOME;
}
/** return $this->_lexer->token['value'];
* Function ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDatetime
*/
public function _Function()
{
$funcName = $this->_lexer->lookahead['value'];
if ($this->isStringFunction($funcName)) { default:
return $this->FunctionsReturningStrings(); $this->syntaxError('Literal');
} else if ($this->isNumericFunction($funcName)) {
return $this->FunctionsReturningNumerics();
} else if ($this->isDatetimeFunction($funcName)) {
return $this->FunctionsReturningDatetime();
} }
$this->syntaxError('Known function.');
}
/**
* Checks whether the function with the given name is a string function
* (a function that returns strings).
*/
public function isStringFunction($funcName)
{
return isset(self::$_STRING_FUNCTIONS[strtolower($funcName)]);
}
/**
* Checks whether the function with the given name is a numeric function
* (a function that returns numerics).
*/
public function isNumericFunction($funcName)
{
return isset(self::$_NUMERIC_FUNCTIONS[strtolower($funcName)]);
}
/**
* Checks whether the function with the given name is a datetime function
* (a function that returns date/time values).
*/
public function isDatetimeFunction($funcName)
{
return isset(self::$_DATETIME_FUNCTIONS[strtolower($funcName)]);
}
/**
* Checks whether the given token is a comparison operator.
*/
public function isComparisonOperator($token)
{
$value = $token['value'];
return $value == '=' || $value == '<' ||
$value == '<=' || $value == '<>' ||
$value == '>' || $value == '>=' ||
$value == '!=';
} }
/** /**
* ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")" * ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")"
*/ */
...@@ -1722,229 +1761,214 @@ class Parser ...@@ -1722,229 +1761,214 @@ class Parser
} }
/** /**
* InExpression ::= StateFieldPathExpression ["NOT"] "IN" "(" (Literal {"," Literal}* | Subselect) ")" * ArithmeticPrimary ::= SingleValuedPathExpression | Literal | "(" SimpleArithmeticExpression ")"
* | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings
* | FunctionsReturningDatetime | IdentificationVariable
*/ */
public function InExpression() public function ArithmeticPrimary()
{ {
$inExpression = new AST\InExpression($this->StateFieldPathExpression()); if ($this->_lexer->lookahead['value'] === '(') {
$this->match('(');
if ($this->_lexer->isNextToken(Lexer::T_NOT)) { $expr = $this->SimpleArithmeticExpression();
$this->match(Lexer::T_NOT); $this->match(')');
$inExpression->setNot(true);
}
$this->match(Lexer::T_IN);
$this->match('(');
if ($this->_lexer->isNextToken(Lexer::T_SELECT)) {
$inExpression->setSubselect($this->Subselect());
} else {
$literals = array();
$literals[] = $this->Literal();
while ($this->_lexer->isNextToken(',')) {
$this->match(',');
$literals[] = $this->Literal();
}
$inExpression->setLiterals($literals); return $expr;
} }
$this->match(')'); switch ($this->_lexer->lookahead['type']) {
case Lexer::T_IDENTIFIER:
$peek = $this->_lexer->glimpse();
return $inExpression; if ($peek['value'] == '(') {
} return $this->FunctionDeclaration();
}
/** if ($peek['value'] == '.') {
* ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")" return $this->SingleValuedPathExpression();
*/ }
public function ExistsExpression()
{
$not = false;
if ($this->_lexer->isNextToken(Lexer::T_NOT)) { return $this->IdentificationVariable();
$this->match(Lexer::T_NOT);
$not = true;
}
$this->match(Lexer::T_EXISTS); case Lexer::T_INPUT_PARAMETER:
$this->match('('); $this->match($this->_lexer->lookahead['value']);
$existsExpression = new AST\ExistsExpression($this->Subselect());
$this->match(')');
$existsExpression->setNot($not);
return $existsExpression; return new AST\InputParameter($this->_lexer->token['value']);
}
/** case Lexer::T_STRING:
* QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")" case Lexer::T_INTEGER:
*/ case Lexer::T_FLOAT:
public function QuantifiedExpression() $this->match($this->_lexer->lookahead['value']);
{
$all = $any = $some = false;
if ($this->_lexer->isNextToken(Lexer::T_ALL)) { return $this->_lexer->token['value'];
$this->match(Lexer::T_ALL);
$all = true;
} else if ($this->_lexer->isNextToken(Lexer::T_ANY)) {
$this->match(Lexer::T_ANY);
$any = true;
} else if ($this->_lexer->isNextToken(Lexer::T_SOME)) {
$this->match(Lexer::T_SOME);
$some = true;
} else {
$this->syntaxError('ALL, ANY or SOME');
}
$this->match('('); default:
$qExpr = new AST\QuantifiedExpression($this->Subselect()); $peek = $this->_lexer->glimpse();
$this->match(')');
$qExpr->setAll($all); if ($peek['value'] == '(') {
$qExpr->setAny($any); if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
$qExpr->setSome($some); return $this->AggregateExpression();
}
return $qExpr; return $this->FunctionsReturningStrings();
} else {
$this->syntaxError();
}
}
} }
/** /**
* Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] * StringExpression ::= StringPrimary | "(" Subselect ")"
*/ */
public function Subselect() public function StringExpression()
{ {
$this->_beginDeferredPathExpressionStack(); if ($this->_lexer->lookahead['value'] === '(') {
$subselect = new AST\Subselect($this->SimpleSelectClause(), $this->SubselectFromClause()); $peek = $this->_lexer->glimpse();
$this->_processDeferredPathExpressionStack();
$subselect->setWhereClause(
$this->_lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null
);
$subselect->setGroupByClause(
$this->_lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null
);
$subselect->setHavingClause( if ($peek['type'] === Lexer::T_SELECT) {
$this->_lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null $this->match('(');
); $expr = $this->Subselect();
$this->match(')');
$subselect->setOrderByClause( return $expr;
$this->_lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null }
); }
return $subselect; return $this->StringPrimary();
} }
/** /**
* SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression * StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression
*/ */
public function SimpleSelectClause() public function StringPrimary()
{ {
$distinct = false; if ($this->_lexer->lookahead['type'] === Lexer::T_IDENTIFIER) {
$this->match(Lexer::T_SELECT); $peek = $this->_lexer->glimpse();
if ($this->_lexer->isNextToken(Lexer::T_DISTINCT)) { if ($peek['value'] == '.') {
$this->match(Lexer::T_DISTINCT); return $this->StateFieldPathExpression();
$distinct = true; } else if ($peek['value'] == '(') {
} return $this->FunctionsReturningStrings();
} else {
$this->syntaxError("'.' or '('");
}
} else if ($this->_lexer->lookahead['type'] === Lexer::T_STRING) {
$this->match(Lexer::T_STRING);
$simpleSelectClause = new AST\SimpleSelectClause($this->SimpleSelectExpression()); return $this->_lexer->token['value'];
$simpleSelectClause->setDistinct($distinct); } else if ($this->_lexer->lookahead['type'] === Lexer::T_INPUT_PARAMETER) {
$this->match(Lexer::T_INPUT_PARAMETER);
return $simpleSelectClause; return new AST\InputParameter($this->_lexer->token['value']);
} else if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
return $this->AggregateExpression();
}
$this->syntaxError('StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression');
} }
/** /**
* SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}* * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression
*/ */
public function SubselectFromClause() public function EntityExpression()
{ {
$this->match(Lexer::T_FROM); $glimpse = $this->_lexer->glimpse();
$identificationVariables = array();
$identificationVariables[] = $this->SubselectIdentificationVariableDeclaration(); if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER) && $glimpse['value'] === '.') {
return $this->SingleValuedAssociationPathExpression();
while ($this->_lexer->isNextToken(',')) {
$this->match(',');
$identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
} }
return new AST\SubselectFromClause($identificationVariables); return $this->SimpleEntityExpression();
} }
/** /**
* SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable) * SimpleEntityExpression ::= IdentificationVariable | InputParameter
*/ */
public function SubselectIdentificationVariableDeclaration() public function SimpleEntityExpression()
{ {
$peek = $this->_lexer->glimpse(); if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
$this->match(Lexer::T_INPUT_PARAMETER);
if ($peek['value'] == '.') { return new AST\InputParameter($this->_lexer->token['value']);
$subselectIdentificationVarDecl = new AST\SubselectIdentificationVariableDeclaration;
$subselectIdentificationVarDecl->setAssociationPathExpression($this->AssociationPathExpression());
$this->match(Lexer::T_AS);
$this->match(Lexer::T_IDENTIFIER);
$subselectIdentificationVarDecl->setAliasIdentificationVariable($this->_lexer->token['value']);
return $subselectIdentificationVarDecl;
} }
return $this->IdentificationVariableDeclaration(); return $this->IdentificationVariable();
} }
/** /**
* SimpleSelectExpression ::= StateFieldPathExpression | IdentificationVariable | (AggregateExpression [["AS"] ResultVariable]) * AggregateExpression ::=
* ("AVG" | "MAX" | "MIN" | "SUM") "(" ["DISTINCT"] StateFieldPathExpression ")" |
* "COUNT" "(" ["DISTINCT"] (IdentificationVariable | SingleValuedPathExpression) ")"
*/ */
public function SimpleSelectExpression() public function AggregateExpression()
{ {
if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) { $isDistinct = false;
// SingleValuedPathExpression | IdentificationVariable $functionName = '';
$peek = $this->_lexer->glimpse();
if ($peek['value'] == '.') {
return new AST\SimpleSelectExpression($this->StateFieldPathExpression());
}
$this->match($this->_lexer->lookahead['value']);
return new AST\SimpleSelectExpression($this->_lexer->token['value']); if ($this->_lexer->isNextToken(Lexer::T_COUNT)) {
} else { $this->match(Lexer::T_COUNT);
$expr = new AST\SimpleSelectExpression($this->AggregateExpression()); $functionName = $this->_lexer->token['value'];
$this->match('(');
if ($this->_lexer->isNextToken(Lexer::T_AS)) { if ($this->_lexer->isNextToken(Lexer::T_DISTINCT)) {
$this->match(Lexer::T_AS); $this->match(Lexer::T_DISTINCT);
$isDistinct = true;
} }
if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) { $pathExp = $this->SingleValuedPathExpression();
$expr->setFieldIdentificationVariable($this->ResultVariable()); $this->match(')');
} else {
if ($this->_lexer->isNextToken(Lexer::T_AVG)) {
$this->match(Lexer::T_AVG);
} else if ($this->_lexer->isNextToken(Lexer::T_MAX)) {
$this->match(Lexer::T_MAX);
} else if ($this->_lexer->isNextToken(Lexer::T_MIN)) {
$this->match(Lexer::T_MIN);
} else if ($this->_lexer->isNextToken(Lexer::T_SUM)) {
$this->match(Lexer::T_SUM);
} else {
$this->syntaxError('One of: MAX, MIN, AVG, SUM, COUNT');
} }
return $expr; $functionName = $this->_lexer->token['value'];
$this->match('(');
$pathExp = $this->StateFieldPathExpression();
$this->match(')');
} }
return new AST\AggregateExpression($functionName, $pathExp, $isDistinct);
} }
/** /**
* Literal ::= string | char | integer | float | boolean | InputParameter * QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")"
*
* @todo Rip out InputParameter. Thats not a literal.
*/ */
public function Literal() public function QuantifiedExpression()
{ {
switch ($this->_lexer->lookahead['type']) { $all = $any = $some = false;
case Lexer::T_INPUT_PARAMETER:
$this->match($this->_lexer->lookahead['value']);
return new AST\InputParameter($this->_lexer->token['value']); if ($this->_lexer->isNextToken(Lexer::T_ALL)) {
$this->match(Lexer::T_ALL);
$all = true;
} else if ($this->_lexer->isNextToken(Lexer::T_ANY)) {
$this->match(Lexer::T_ANY);
$any = true;
} else if ($this->_lexer->isNextToken(Lexer::T_SOME)) {
$this->match(Lexer::T_SOME);
$some = true;
} else {
$this->syntaxError('ALL, ANY or SOME');
}
case Lexer::T_STRING: $this->match('(');
case Lexer::T_INTEGER: $qExpr = new AST\QuantifiedExpression($this->Subselect());
case Lexer::T_FLOAT: $this->match(')');
$this->match($this->_lexer->lookahead['value']);
return $this->_lexer->token['value']; $qExpr->setAll($all);
$qExpr->setAny($any);
$qExpr->setSome($some);
default: return $qExpr;
$this->syntaxError('Literal');
}
} }
/** /**
...@@ -1972,109 +1996,139 @@ class Parser ...@@ -1972,109 +1996,139 @@ class Parser
} }
/** /**
* ArithmeticPrimary ::= SingleValuedPathExpression | Literal | "(" SimpleArithmeticExpression ")" * ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression )
* | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings *
* | FunctionsReturningDatetime | IdentificationVariable * @return AST\ComparisonExpression
* @todo Semantical checks whether $leftExpr $operator and $rightExpr are compatible.
*/ */
public function ArithmeticPrimary() public function ComparisonExpression()
{ {
if ($this->_lexer->lookahead['value'] === '(') { $peek = $this->_lexer->glimpse();
$this->match('(');
$expr = $this->SimpleArithmeticExpression();
$this->match(')');
return $expr; $leftExpr = $this->ArithmeticExpression();
$operator = $this->ComparisonOperator();
if ($this->_isNextAllAnySome()) {
$rightExpr = $this->QuantifiedExpression();
} else {
$rightExpr = $this->ArithmeticExpression();
} }
switch ($this->_lexer->lookahead['type']) { return new AST\ComparisonExpression($leftExpr, $operator, $rightExpr);
case Lexer::T_IDENTIFIER: }
$peek = $this->_lexer->glimpse();
if ($peek['value'] == '(') { /**
return $this->_Function(); * InExpression ::= StateFieldPathExpression ["NOT"] "IN" "(" (Literal {"," Literal}* | Subselect) ")"
} */
public function InExpression()
{
$inExpression = new AST\InExpression($this->StateFieldPathExpression());
if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
$this->match(Lexer::T_NOT);
$inExpression->setNot(true);
}
$this->match(Lexer::T_IN);
$this->match('(');
if ($this->_lexer->isNextToken(Lexer::T_SELECT)) {
$inExpression->setSubselect($this->Subselect());
} else {
$literals = array();
$literals[] = $this->Literal();
while ($this->_lexer->isNextToken(',')) {
$this->match(',');
$literals[] = $this->Literal();
}
if ($peek['value'] == '.') { $inExpression->setLiterals($literals);
return $this->SingleValuedPathExpression(); }
}
return $this->IdentificationVariable(); $this->match(')');
case Lexer::T_INPUT_PARAMETER: return $inExpression;
$this->match($this->_lexer->lookahead['value']); }
return new AST\InputParameter($this->_lexer->token['value']); /**
* LikeExpression ::= StringExpression ["NOT"] "LIKE" (string | input_parameter) ["ESCAPE" char]
*/
public function LikeExpression()
{
$stringExpr = $this->StringExpression();
$isNot = false;
case Lexer::T_STRING: if ($this->_lexer->lookahead['type'] === Lexer::T_NOT) {
case Lexer::T_INTEGER: $this->match(Lexer::T_NOT);
case Lexer::T_FLOAT: $isNot = true;
$this->match($this->_lexer->lookahead['value']); }
return $this->_lexer->token['value']; $this->match(Lexer::T_LIKE);
default: if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
$peek = $this->_lexer->glimpse(); $this->match(Lexer::T_INPUT_PARAMETER);
$stringPattern = new AST\InputParameter($this->_lexer->token['value']);
} else {
$this->match(Lexer::T_STRING);
$stringPattern = $this->_lexer->token['value'];
}
if ($peek['value'] == '(') { $escapeChar = null;
if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
return $this->AggregateExpression();
}
return $this->FunctionsReturningStrings(); if ($this->_lexer->lookahead['type'] === Lexer::T_ESCAPE) {
} else { $this->match(Lexer::T_ESCAPE);
$this->syntaxError(); $this->match(Lexer::T_STRING);
} $escapeChar = $this->_lexer->token['value'];
} }
return new AST\LikeExpression($stringExpr, $stringPattern, $isNot, $escapeChar);
} }
/** /**
* FunctionsReturningStrings ::= * NullComparisonExpression ::= (SingleValuedPathExpression | InputParameter) "IS" ["NOT"] "NULL"
* "CONCAT" "(" StringPrimary "," StringPrimary ")" |
* "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
* "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" |
* "LOWER" "(" StringPrimary ")" |
* "UPPER" "(" StringPrimary ")"
*/ */
public function FunctionsReturningStrings() public function NullComparisonExpression()
{ {
$funcNameLower = strtolower($this->_lexer->lookahead['value']); if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
$funcClass = self::$_STRING_FUNCTIONS[$funcNameLower]; $this->match(Lexer::T_INPUT_PARAMETER);
$function = new $funcClass($funcNameLower); $expr = new AST\InputParameter($this->_lexer->token['value']);
$function->parse($this); } else {
$expr = $this->SingleValuedPathExpression();
}
return $function; $nullCompExpr = new AST\NullComparisonExpression($expr);
} $this->match(Lexer::T_IS);
/** if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
* FunctionsReturningNumerics ::= $this->match(Lexer::T_NOT);
* "LENGTH" "(" StringPrimary ")" | $nullCompExpr->setNot(true);
* "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" | }
* "ABS" "(" SimpleArithmeticExpression ")" |
* "SQRT" "(" SimpleArithmeticExpression ")" |
* "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
* "SIZE" "(" CollectionValuedPathExpression ")"
*/
public function FunctionsReturningNumerics()
{
$funcNameLower = strtolower($this->_lexer->lookahead['value']);
$funcClass = self::$_NUMERIC_FUNCTIONS[$funcNameLower];
$function = new $funcClass($funcNameLower);
$function->parse($this);
return $function; $this->match(Lexer::T_NULL);
return $nullCompExpr;
} }
/** /**
* FunctionsReturningDateTime ::= "CURRENT_DATE" | "CURRENT_TIME" | "CURRENT_TIMESTAMP" * ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")"
*/ */
public function FunctionsReturningDatetime() public function ExistsExpression()
{ {
$funcNameLower = strtolower($this->_lexer->lookahead['value']); $not = false;
$funcClass = self::$_DATETIME_FUNCTIONS[$funcNameLower];
$function = new $funcClass($funcNameLower);
$function->parse($this);
return $function; if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
$this->match(Lexer::T_NOT);
$not = true;
}
$this->match(Lexer::T_EXISTS);
$this->match('(');
$existsExpression = new AST\ExistsExpression($this->Subselect());
$this->match(')');
$existsExpression->setNot($not);
return $existsExpression;
} }
/** /**
...@@ -2124,120 +2178,72 @@ class Parser ...@@ -2124,120 +2178,72 @@ class Parser
} }
} }
/** /**
* LikeExpression ::= StringExpression ["NOT"] "LIKE" (string | input_parameter) ["ESCAPE" char] * FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDatetime
*/ */
public function LikeExpression() public function FunctionDeclaration()
{ {
$stringExpr = $this->StringExpression(); $funcName = $this->_lexer->lookahead['value'];
$isNot = false;
if ($this->_lexer->lookahead['type'] === Lexer::T_NOT) {
$this->match(Lexer::T_NOT);
$isNot = true;
}
$this->match(Lexer::T_LIKE);
if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
$this->match(Lexer::T_INPUT_PARAMETER);
$stringPattern = new AST\InputParameter($this->_lexer->token['value']);
} else {
$this->match(Lexer::T_STRING);
$stringPattern = $this->_lexer->token['value'];
}
$escapeChar = null;
if ($this->_lexer->lookahead['type'] === Lexer::T_ESCAPE) { if ($this->_isStringFunction($funcName)) {
$this->match(Lexer::T_ESCAPE); return $this->FunctionsReturningStrings();
$this->match(Lexer::T_STRING); } else if ($this->_isNumericFunction($funcName)) {
$escapeChar = $this->_lexer->token['value']; return $this->FunctionsReturningNumerics();
} else if ($this->_isDatetimeFunction($funcName)) {
return $this->FunctionsReturningDatetime();
} }
return new AST\LikeExpression($stringExpr, $stringPattern, $isNot, $escapeChar); $this->syntaxError('Known function.');
} }
/** /**
* StringExpression ::= StringPrimary | "(" Subselect ")" * FunctionsReturningNumerics ::=
* "LENGTH" "(" StringPrimary ")" |
* "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" |
* "ABS" "(" SimpleArithmeticExpression ")" |
* "SQRT" "(" SimpleArithmeticExpression ")" |
* "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
* "SIZE" "(" CollectionValuedPathExpression ")"
*/ */
public function StringExpression() public function FunctionsReturningNumerics()
{ {
if ($this->_lexer->lookahead['value'] === '(') { $funcNameLower = strtolower($this->_lexer->lookahead['value']);
$peek = $this->_lexer->glimpse(); $funcClass = self::$_NUMERIC_FUNCTIONS[$funcNameLower];
$function = new $funcClass($funcNameLower);
if ($peek['type'] === Lexer::T_SELECT) { $function->parse($this);
$this->match('(');
$expr = $this->Subselect();
$this->match(')');
return $expr;
}
}
return $this->StringPrimary(); return $function;
} }
/** /**
* StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression * FunctionsReturningDateTime ::= "CURRENT_DATE" | "CURRENT_TIME" | "CURRENT_TIMESTAMP"
*/ */
public function StringPrimary() public function FunctionsReturningDatetime()
{ {
if ($this->_lexer->lookahead['type'] === Lexer::T_IDENTIFIER) { $funcNameLower = strtolower($this->_lexer->lookahead['value']);
$peek = $this->_lexer->glimpse(); $funcClass = self::$_DATETIME_FUNCTIONS[$funcNameLower];
$function = new $funcClass($funcNameLower);
if ($peek['value'] == '.') { $function->parse($this);
return $this->StateFieldPathExpression();
} else if ($peek['value'] == '(') {
return $this->FunctionsReturningStrings();
} else {
$this->syntaxError("'.' or '('");
}
} else if ($this->_lexer->lookahead['type'] === Lexer::T_STRING) {
$this->match(Lexer::T_STRING);
return $this->_lexer->token['value'];
} else if ($this->_lexer->lookahead['type'] === Lexer::T_INPUT_PARAMETER) {
$this->match(Lexer::T_INPUT_PARAMETER);
return new AST\InputParameter($this->_lexer->token['value']);
} else if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
return $this->AggregateExpression();
}
$this->syntaxError('StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression');
}
/** return $function;
* Registers a custom function that returns strings.
*
* @param string $name The function name.
* @param string $class The class name of the function implementation.
*/
public static function registerStringFunction($name, $class)
{
self::$_STRING_FUNCTIONS[$name] = $class;
} }
/** /**
* Registers a custom function that returns numerics. * FunctionsReturningStrings ::=
* * "CONCAT" "(" StringPrimary "," StringPrimary ")" |
* @param string $name The function name. * "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
* @param string $class The class name of the function implementation. * "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" |
* "LOWER" "(" StringPrimary ")" |
* "UPPER" "(" StringPrimary ")"
*/ */
public static function registerNumericFunction($name, $class) public function FunctionsReturningStrings()
{ {
self::$_NUMERIC_FUNCTIONS[$name] = $class; $funcNameLower = strtolower($this->_lexer->lookahead['value']);
} $funcClass = self::$_STRING_FUNCTIONS[$funcNameLower];
$function = new $funcClass($funcNameLower);
$function->parse($this);
/** return $function;
* Registers a custom function that returns date/time values.
*
* @param string $name The function name.
* @param string $class The class name of the function implementation.
*/
public static function registerDatetimeFunction($name, $class)
{
self::$_DATETIME_FUNCTIONS[$name] = $class;
} }
} }
...@@ -48,7 +48,7 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase ...@@ -48,7 +48,7 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase
$parser = new \Doctrine\ORM\Query\Parser($query); $parser = new \Doctrine\ORM\Query\Parser($query);
// We do NOT test SQL construction here. That only unnecessarily slows down the tests! // We do NOT test SQL construction here. That only unnecessarily slows down the tests!
$parser->setSqlTreeWalker(new \Doctrine\Tests\Mocks\MockTreeWalker); $parser->setTreeWalker(new \Doctrine\Tests\Mocks\MockTreeWalker);
return $parser->parse(); return $parser->parse();
} }
......
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