Commit 77206615 authored by guilhermeblanco's avatar guilhermeblanco

[2.0] Implemented more TODO items in DQL Parser. Optimized PathExpression....

[2.0] Implemented more TODO items in DQL Parser. Optimized PathExpression. Changed wrong grammar rule name in EBNF.
parent 92214eaf
...@@ -3,14 +3,12 @@ ...@@ -3,14 +3,12 @@
namespace Doctrine\ORM\Query\AST; namespace Doctrine\ORM\Query\AST;
/** /**
* JoinAssociationPathExpression ::= JoinCollectionValuedPathExpression | JoinSingleValuedAssociationPathExpression * JoinAssociationPathExpression ::= IdentificationVariable "." (SingleValuedAssociationField | CollectionValuedAssociationField)
* JoinCollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField
* JoinSingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField
* *
* @author robo * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @todo Rename: JoinAssociationPathExpression * @author Roman Borschel
*/ */
class JoinPathExpression extends Node class JoinAssociationPathExpression extends Node
{ {
private $_identificationVariable; private $_identificationVariable;
private $_assocField; private $_assocField;
......
...@@ -26,18 +26,18 @@ namespace Doctrine\ORM\Query\AST; ...@@ -26,18 +26,18 @@ namespace Doctrine\ORM\Query\AST;
*/ */
class PathExpression extends Node class PathExpression extends Node
{ {
const TYPE_SINGLE_VALUED_PATH_EXPRESSION = 1;
const TYPE_COLLECTION_VALUED_ASSOCIATION = 2; const TYPE_COLLECTION_VALUED_ASSOCIATION = 2;
const TYPE_SINGLE_VALUED_ASSOCIATION = 4; const TYPE_SINGLE_VALUED_ASSOCIATION = 4;
const TYPE_STATE_FIELD = 8; const TYPE_STATE_FIELD = 8;
private $_type; private $_type;
private $_expectedType;
private $_identificationVariable; private $_identificationVariable;
private $_parts; private $_parts;
public function __construct($type, $identificationVariable, array $parts) public function __construct($expectedType, $identificationVariable, array $parts)
{ {
$this->_type = $type; $this->_expectedType = $expectedType;
$this->_identificationVariable = $identificationVariable; $this->_identificationVariable = $identificationVariable;
$this->_parts = $parts; $this->_parts = $parts;
} }
...@@ -52,6 +52,19 @@ class PathExpression extends Node ...@@ -52,6 +52,19 @@ class PathExpression extends Node
return $this->_parts; return $this->_parts;
} }
public function setExpectedType($type)
{
$this->_expectedType;
}
public function getExpectedType()
{
return $this->_expectedType;
}
/**
* INTERNAL
*/
public function setType($type) public function setType($type)
{ {
$this->_type = $type; $this->_type = $type;
......
...@@ -316,7 +316,7 @@ class Parser ...@@ -316,7 +316,7 @@ class Parser
// Building informative message // Building informative message
$message = 'line 0, col ' . (isset($token['position']) ? $token['position'] : '-1') $message = 'line 0, col ' . (isset($token['position']) ? $token['position'] : '-1')
. " near '" . substr($dql, $token['position'], $length) . "'): Error: " . $message; . " near '" . substr($dql, $token['position'], $length) . "': Error: " . $message;
throw \Doctrine\ORM\Query\QueryException::semanticalError($message); throw \Doctrine\ORM\Query\QueryException::semanticalError($message);
} }
...@@ -440,27 +440,7 @@ class Parser ...@@ -440,27 +440,7 @@ class Parser
$exprStack = array_pop($this->_deferredPathExpressionStacks); $exprStack = array_pop($this->_deferredPathExpressionStacks);
foreach ($exprStack as $expr) { foreach ($exprStack as $expr) {
switch ($expr->getType()) { $this->_validatePathExpression($expr);
case AST\PathExpression::TYPE_SINGLE_VALUED_PATH_EXPRESSION:
$this->_validateSingleValuedPathExpression($expr);
break;
case AST\PathExpression::TYPE_STATE_FIELD:
$this->_validateStateFieldPathExpression($expr);
break;
case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION:
$this->_validateSingleValuedAssociationPathExpression($expr);
break;
case AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION:
$this->_validateCollectionValuedAssociationPathExpression($expr);
break;
default:
$this->semanticalError('Encountered invalid PathExpression.');
break;
}
} }
} }
...@@ -474,11 +454,15 @@ class Parser ...@@ -474,11 +454,15 @@ class Parser
* CollectionValuedPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* CollectionValuedAssociationField * CollectionValuedPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* CollectionValuedAssociationField
* *
* @param PathExpression $pathExpression * @param PathExpression $pathExpression
* @return boolean * @return integer
*/ */
private function _validatePathExpression(AST\PathExpression $pathExpression) private function _validatePathExpression(AST\PathExpression $pathExpression)
{ {
$class = $this->_queryComponents[$pathExpression->getIdentificationVariable()]['metadata']; $identificationVariable = $pathExpression->getIdentificationVariable();
$this->_validateIdentificationVariable($identificationVariable);
$class = $this->_queryComponents[$identificationVariable]['metadata'];
$stateField = $collectionField = null; $stateField = $collectionField = null;
foreach ($pathExpression->getParts() as $field) { foreach ($pathExpression->getParts() as $field) {
...@@ -506,6 +490,7 @@ class Parser ...@@ -506,6 +490,7 @@ class Parser
} }
} }
// Recognize correct expression type
$expressionType = null; $expressionType = null;
if ($stateField !== null) { if ($stateField !== null) {
...@@ -514,78 +499,65 @@ class Parser ...@@ -514,78 +499,65 @@ class Parser
$expressionType = AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION; $expressionType = AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION;
} else { } else {
$expressionType = AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION; $expressionType = AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION;
} }
// Validate if PathExpression is one of the expected types
$expectedType = $pathExpression->getExpectedType();
if ( ! ($expectedType & $expressionType)) {
// We need to recognize which was expected type(s)
$expectedStringTypes = array();
// Validate state field type (field/column)
if ($expectedType & AST\PathExpression::TYPE_STATE_FIELD) {
$expectedStringTypes[] = 'StateFieldPathExpression';
}
// Validate single valued association (*-to-one)
if ($expectedType & AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION) {
$expectedStringTypes[] = 'SingleValuedAssociationField';
}
// Validate single valued association (*-to-many)
if ($expectedType & AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION) {
$expectedStringTypes[] = 'CollectionValuedAssociationField';
}
// Build the error message
$semanticalError = 'Invalid PathExpression.';
if (count($expectedStringTypes) == 1) {
$semanticalError .= ' Must be a ' . $expectedStringTypes[0] . '.';
} else {
$semanticalError .= ' ' . implode(' or ', $expectedStringTypes) . ' expected.';
}
$this->semanticalError($semanticalError);
}
// We need to force the type in PathExpression // We need to force the type in PathExpression
$pathExpression->setType($expressionType); $pathExpression->setType($expressionType);
return $expressionType; return $expressionType;
} }
/**
* Validates that the given <tt>PathExpression</tt> is a semantically correct
* SingleValuedPathExpression as specified in the grammar.
*
* @param PathExpression $pathExpression
*/
private function _validateSingleValuedPathExpression(AST\PathExpression $pathExpression)
{
$pathExprType = $this->_validatePathExpression($pathExpression);
if (
$pathExprType !== AST\PathExpression::TYPE_STATE_FIELD &&
$pathExprType !== AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION
) {
$this->semanticalError('SingleValuedAssociationField or StateField expected.');
}
}
/** /**
* Validates that the given <tt>PathExpression</tt> is a semantically correct * Validates that the given <tt>IdentificationVariable</tt> is a semantically correct.
* StateFieldPathExpression as specified in the grammar. * It must exist in query components list.
* *
* @param PathExpression $pathExpression * @param string $identificationVariable
*/ */
private function _validateStateFieldPathExpression(AST\PathExpression $pathExpression) private function _validateIdentificationVariable($identificationVariable)
{ {
$pathExprType = $this->_validatePathExpression($pathExpression); if ( ! isset($this->_queryComponents[$identificationVariable])) {
$this->semanticalError(
if ($pathExprType !== AST\PathExpression::TYPE_STATE_FIELD) { 'Invalid IdentificationVariable. Could not find \'' .
$this->semanticalError('Invalid StateFieldPathExpression. Must end in a state field.'); $identificationVariable . '\' in query components.'
} );
}
/**
* Validates that the given <tt>PathExpression</tt> is a semantically correct
* CollectionValuedPathExpression as specified in the grammar.
*
* @param PathExpression $pathExpression
*/
private function _validateCollectionValuedPathExpression(AST\PathExpression $pathExpression)
{
$pathExprType = $this->_validatePathExpression($pathExpression);
if ($pathExprType !== AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION) {
$this->semanticalError('Invalid CollectionValuedAssociationField. Must end in a collection-valued field.');
} }
} }
/**
* Validates that the given <tt>PathExpression</tt> is a semantically correct
* SingleValuedAssociationPathExpression as specified in the grammar.
*
* @param PathExpression $pathExpression
*/
private function _validateSingleValuedAssociationPathExpression(AST\PathExpression $pathExpression)
{
$pathExprType = $this->_validatePathExpression($pathExpression);
if ($pathExprType !== AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION) {
$this->semanticalError('Invalid SingleValuedAssociationField. Must end in a single valued association field.');
}
}
/** /**
* QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement * QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement
*/ */
...@@ -711,30 +683,40 @@ class Parser ...@@ -711,30 +683,40 @@ class Parser
/** /**
* JoinPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField) * JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField)
*/ */
public function JoinPathExpression() public function JoinAssociationPathExpression()
{ {
$identificationVariable = $this->IdentificationVariable(); $identificationVariable = $this->IdentificationVariable();
$this->match('.'); $this->match('.');
$this->match(Lexer::T_IDENTIFIER); $this->match(Lexer::T_IDENTIFIER);
$field = $this->_lexer->token['value'];
return new AST\JoinPathExpression(
$identificationVariable, $this->_lexer->token['value'] // Validating IdentificationVariable (it was already defined previously)
); $this->_validateIdentificationVariable($identificationVariable);
// Validating association field (*-to-one or *-to-many)
$class = $this->_queryComponents[$identificationVariable]['metadata'];
if ( ! isset($class->associationMappings[$field])) {
$this->semanticalError('Class ' . $class->name . ' has no field named ' . $field);
}
return new AST\JoinAssociationPathExpression($identificationVariable, $field);
} }
/** /**
* Parses an arbitrary path expression without any semantic validation. * Parses an arbitrary path expression. Applies or defer semantical validation
* based on expected types.
* *
* PathExpression ::= IdentificationVariable "." {identifier "."}* identifier * PathExpression ::= IdentificationVariable "." {identifier "."}* identifier
* *
* @param integer $expectedType
* @return PathExpression * @return PathExpression
*/ */
public function PathExpression($type) public function PathExpression($expectedType)
{ {
$identificationVariable = $this->IdentificationVariable(); $identificationVariable = $this->IdentificationVariable();
$parts = array(); $parts = array();
do { do {
...@@ -744,17 +726,33 @@ class Parser ...@@ -744,17 +726,33 @@ class Parser
$parts[] = $this->_lexer->token['value']; $parts[] = $this->_lexer->token['value'];
} while ($this->_lexer->isNextToken('.')); } while ($this->_lexer->isNextToken('.'));
return new AST\PathExpression($type, $identificationVariable, $parts); // Creating AST node
$pathExpr = new AST\PathExpression($expectedType, $identificationVariable, $parts);
// Defer PathExpression validation if requested to be defered
if ( ! empty($this->_deferredPathExpressionStacks)) {
$exprStack = array_pop($this->_deferredPathExpressionStacks);
$exprStack[] = $pathExpr;
array_push($this->_deferredPathExpressionStacks, $exprStack);
return $pathExpr;
}
// Apply PathExpression validation normally (not in defer mode)
$this->_validatePathExpression($pathExpr);
return $pathExpr;
} }
/** /**
* AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression * AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression
*
* @todo Implement this grammar rule which is used in SubselectFromClause
*/ */
public function AssociationPathExpression() public function AssociationPathExpression()
{ {
return $this->PathExpression(
AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION |
AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION
);
} }
/** /**
...@@ -762,19 +760,10 @@ class Parser ...@@ -762,19 +760,10 @@ class Parser
*/ */
public function SingleValuedPathExpression() public function SingleValuedPathExpression()
{ {
$pathExpr = $this->PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_PATH_EXPRESSION); return $this->PathExpression(
AST\PathExpression::TYPE_STATE_FIELD |
if ( ! empty($this->_deferredPathExpressionStacks)) { AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION
$exprStack = array_pop($this->_deferredPathExpressionStacks); );
$exprStack[] = $pathExpr;
array_push($this->_deferredPathExpressionStacks, $exprStack);
return $pathExpr;
}
$this->_validateSingleValuedPathExpression($pathExpr);
return $pathExpr;
} }
/** /**
...@@ -782,19 +771,7 @@ class Parser ...@@ -782,19 +771,7 @@ class Parser
*/ */
public function StateFieldPathExpression() public function StateFieldPathExpression()
{ {
$pathExpr = $this->PathExpression(AST\PathExpression::TYPE_STATE_FIELD); return $this->PathExpression(AST\PathExpression::TYPE_STATE_FIELD);
if ( ! empty($this->_deferredPathExpressionStacks)) {
$exprStack = array_pop($this->_deferredPathExpressionStacks);
$exprStack[] = $pathExpr;
array_push($this->_deferredPathExpressionStacks, $exprStack);
return $pathExpr;
}
$this->_validateStateFieldPathExpression($pathExpr);
return $pathExpr;
} }
/** /**
...@@ -802,19 +779,7 @@ class Parser ...@@ -802,19 +779,7 @@ class Parser
*/ */
public function SingleValuedAssociationPathExpression() public function SingleValuedAssociationPathExpression()
{ {
$pathExpr = $this->PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION); return $this->PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION);
if ( ! empty($this->_deferredPathExpressionStacks)) {
$exprStack = array_pop($this->_deferredPathExpressionStacks);
$exprStack[] = $pathExpr;
array_push($this->_deferredPathExpressionStacks, $exprStack);
return $pathExpr;
}
$this->_validateSingleValuedAssociationPathExpression($pathExpr);
return $pathExpr;
} }
/** /**
...@@ -822,39 +787,17 @@ class Parser ...@@ -822,39 +787,17 @@ class Parser
*/ */
public function CollectionValuedPathExpression() public function CollectionValuedPathExpression()
{ {
$pathExpr = $this->PathExpression(AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION); return $this->PathExpression(AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION);
if ( ! empty($this->_deferredPathExpressionStacks)) {
$exprStack = array_pop($this->_deferredPathExpressionStacks);
$exprStack[] = $pathExpr;
array_push($this->_deferredPathExpressionStacks, $exprStack);
return $pathExpr;
}
$this->_validateCollectionValuedPathExpression($pathExpr);
return $pathExpr;
} }
/** /**
* SimpleStateFieldPathExpression ::= IdentificationVariable "." StateField * SimpleStateFieldPathExpression ::= IdentificationVariable "." StateField
*
* @todo We should only allow 1 single part here (state field). It is used by INDEX BY clause.
*/ */
public function SimpleStateFieldPathExpression() public function SimpleStateFieldPathExpression()
{ {
$pathExpr = $this->PathExpression(AST\PathExpression::TYPE_STATE_FIELD); return $this->PathExpression(AST\PathExpression::TYPE_STATE_FIELD);
if ( ! empty($this->_deferredPathExpressionStacks)) {
$exprStack = array_pop($this->_deferredPathExpressionStacks);
$exprStack[] = $pathExpr;
array_push($this->_deferredPathExpressionStacks, $exprStack);
return $pathExpr;
}
$this->_validateStateFieldPathExpression($pathExpr);
return $pathExpr;
} }
...@@ -1288,7 +1231,7 @@ class Parser ...@@ -1288,7 +1231,7 @@ class Parser
} }
/** /**
* Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinPathExpression * Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinAssociationPathExpression
* ["AS"] AliasIdentificationVariable [("ON" | "WITH") ConditionalExpression] * ["AS"] AliasIdentificationVariable [("ON" | "WITH") ConditionalExpression]
*/ */
public function Join() public function Join()
...@@ -1311,7 +1254,7 @@ class Parser ...@@ -1311,7 +1254,7 @@ class Parser
} }
$this->match(Lexer::T_JOIN); $this->match(Lexer::T_JOIN);
$joinPathExpression = $this->JoinPathExpression(); $joinPathExpression = $this->JoinAssociationPathExpression();
if ($this->_lexer->isNextToken(Lexer::T_AS)) { if ($this->_lexer->isNextToken(Lexer::T_AS)) {
$this->match(Lexer::T_AS); $this->match(Lexer::T_AS);
......
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