diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php
index 9d84be4d9..87eda3ebf 100644
--- a/lib/Doctrine/ORM/Query/Parser.php
+++ b/lib/Doctrine/ORM/Query/Parser.php
@@ -66,14 +66,6 @@ class Parser
'current_timestamp' => 'Doctrine\ORM\Query\AST\Functions\CurrentTimestampFunction'
);
- /**
- * The minimum number of tokens read after last detected error before
- * another error can be reported.
- *
- * @var int
- */
- //const MIN_ERROR_DISTANCE = 2;
-
/**
* Path expressions that were encountered during parsing of SelectExpressions
* and still need to be validated.
@@ -134,7 +126,7 @@ class Parser
$this->_query = $query;
$this->_em = $query->getEntityManager();
$this->_lexer = new Lexer($query->getDql());
- $this->_parserResult = new ParserResult;
+ $this->_parserResult = new ParserResult();
}
/**
@@ -150,9 +142,8 @@ class Parser
{
$key = (is_string($token)) ? 'value' : 'type';
- if ( ! ($this->_lexer->lookahead[$key] === $token)) {
+ if ( ! ($this->_lexer->lookahead[$key] === $token))
$this->syntaxError($this->_lexer->getLiteral($token));
- }
$this->_lexer->moveNext();
}
@@ -171,12 +162,10 @@ class Parser
// Deep = true cleans peek and also any previously defined errors
if ($deep) {
$this->_lexer->resetPeek();
- //$this->_errors = array();
}
$this->_lexer->token = null;
$this->_lexer->lookahead = null;
- //$this->_errorDistance = self::MIN_ERROR_DISTANCE;
}
/**
@@ -191,7 +180,6 @@ class Parser
// Check for end of string
if ($this->_lexer->lookahead !== null) {
- //var_dump($this->_lexer->lookahead);
$this->syntaxError('end of string');
}
@@ -205,6 +193,16 @@ class Parser
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.
@@ -225,6 +223,16 @@ class Parser
{
return $this->_parserResult;
}
+
+ /**
+ * Gets the EntityManager used by the parser.
+ *
+ * @return EntityManager
+ */
+ public function getEntityManager()
+ {
+ return $this->_em;
+ }
/**
* Generates a new syntax error.
@@ -238,10 +246,11 @@ class Parser
$token = $this->_lexer->lookahead;
}
- $message = 'line 0, col ' . (isset($token['position']) ? $token['position'] : '-1') . ': Error: ';
+ $tokenPos = (isset($token['position'])) ? $token['position'] : '-1';
+ $message = "line 0, col {$tokenPos}: Error: ";
if ($expected !== '') {
- $message .= "Expected '$expected', got ";
+ $message .= "Expected '{$expected}', got ";
} else {
$message .= 'Unexpected ';
}
@@ -252,7 +261,7 @@ class Parser
$message .= "'{$this->_lexer->lookahead['value']}'";
}
- throw QueryException::syntaxError($message);
+ throw \Doctrine\ORM\Query\QueryException::syntaxError($message);
}
/**
@@ -276,44 +285,51 @@ class Parser
$message = 'line 0, col ' . (isset($token['position']) ? $token['position'] : '-1')
. " near '" . substr($dql, $token['position'], $length) . "'): Error: " . $message;
- throw QueryException::semanticalError($message);
+ throw \Doctrine\ORM\Query\QueryException::semanticalError($message);
}
-
+
/**
- * Logs new error entry.
- *
- * @param string $message Message to log.
- * @param array $token Token that it was processing.
+ * Peeks beyond the specified token and returns the first token after that one.
*/
- /*protected function _logError($message = '', $token)
- {
- if ($this->_errorDistance >= self::MIN_ERROR_DISTANCE) {
- $message = 'line 0, col ' . $token['position'] . ': ' . $message;
- $this->_errors[] = $message;
- }
-
- $this->_errorDistance = 0;
- }*/
-
- /**
- * Gets the EntityManager used by the parser.
- *
- * @return EntityManager
- */
- public function getEntityManager()
+ private function _peekBeyond($token)
{
- return $this->_em;
+ $peek = $this->_lexer->peek();
+
+ while ($peek['value'] != $token) {
+ $peek = $this->_lexer->peek();
+ }
+
+ $peek = $this->_lexer->peek();
+ $this->_lexer->resetPeek();
+
+ return $peek;
}
/**
* Checks if the next-next (after lookahead) token starts a function.
*
- * @return boolean
+ * @return boolean TRUE if the next-next tokens start a function, FALSE otherwise.
*/
private function _isFunction()
{
- $next = $this->_lexer->glimpse();
- return $next['value'] === '(';
+ $peek = $this->_lexer->peek();
+ $nextpeek = $this->_lexer->peek();
+ $this->_lexer->resetPeek();
+
+ // We deny the COUNT(SELECT * FROM User u) here. COUNT won't be considered a function
+ return ($peek['value'] === '(' && $nextpeek['type'] !== Lexer::T_SELECT);
+ }
+
+ /**
+ * Checks whether the given token type indicates an aggregate function.
+ *
+ * @return boolean TRUE if the token type is an aggregate function, FALSE otherwise.
+ */
+ private function _isAggregateFunction($tokenType)
+ {
+ return $tokenType == Lexer::T_AVG || $tokenType == Lexer::T_MIN ||
+ $tokenType == Lexer::T_MAX || $tokenType == Lexer::T_SUM ||
+ $tokenType == Lexer::T_COUNT;
}
/**
@@ -328,6 +344,168 @@ class Parser
return ($la['value'] === '(' && $next['type'] === Lexer::T_SELECT);
}
+
+ /**
+ * Begins a new stack of deferred path expressions.
+ */
+ private function _beginDeferredPathExpressionStack()
+ {
+ $this->_deferredPathExpressionStacks[] = array();
+ }
+
+ /**
+ * Processes the topmost stack of deferred path expressions.
+ */
+ private function _processDeferredPathExpressionStack()
+ {
+ $exprStack = array_pop($this->_deferredPathExpressionStacks);
+
+ foreach ($exprStack as $expr) {
+ switch ($expr->getType()) {
+ 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;
+ }
+ }
+ }
+
+ /**
+ * Validates that the given PathExpression is a semantically correct for grammar rules:
+ *
+ * AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression
+ * SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression
+ * StateFieldPathExpression ::= IdentificationVariable "." StateField | SingleValuedAssociationPathExpression "." StateField
+ * SingleValuedAssociationPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* SingleValuedAssociationField
+ * CollectionValuedPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* CollectionValuedAssociationField
+ *
+ * @param PathExpression $pathExpression
+ * @return boolean
+ */
+ private function _validatePathExpression(AST\PathExpression $pathExpression)
+ {
+ $class = $this->_queryComponents[$pathExpression->getIdentificationVariable()]['metadata'];
+ $stateField = $collectionField = null;
+
+ foreach ($pathExpression->getParts() as $field) {
+ // Check if it is not in a state field
+ if ($stateField !== null) {
+ $this->semanticalError('Cannot navigate through state field named ' . $stateField);
+ }
+
+ // Check if it is not a collection field
+ if ($collectionField !== null) {
+ $this->semanticalError('Can not navigate through collection-valued field named ' . $collectionField);
+ }
+
+ // Check if field exists
+ if ( ! isset($class->associationMappings[$field]) && ! isset($class->fieldMappings[$field])) {
+ $this->semanticalError('Class ' . $class->name . ' has no field named ' . $field);
+ }
+
+ if (isset($class->fieldMappings[$field])) {
+ $stateField = $field;
+ } else if ($class->associationMappings[$field]->isOneToOne()) {
+ $class = $this->_em->getClassMetadata($class->associationMappings[$field]->targetEntityName);
+ } else {
+ $collectionField = $field;
+ }
+ }
+
+ $expressionType = null;
+
+ if ($stateField !== null) {
+ $expressionType = AST\PathExpression::TYPE_STATE_FIELD;
+ } else if ($collectionField !== null) {
+ $expressionType = AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION;
+ } else {
+ $expressionType = AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION;
+ }
+
+ // We need to force the type in PathExpression
+ $pathExpression->setType($expressionType);
+
+ return $expressionType;
+ }
+
+ /**
+ * Validates that the given PathExpression 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 PathExpression is a semantically correct
+ * StateFieldPathExpression as specified in the grammar.
+ *
+ * @param PathExpression $pathExpression
+ */
+ private function _validateStateFieldPathExpression(AST\PathExpression $pathExpression)
+ {
+ $pathExprType = $this->_validatePathExpression($pathExpression);
+
+ if ($pathExprType !== AST\PathExpression::TYPE_STATE_FIELD) {
+ $this->semanticalError('Invalid StateFieldPathExpression. Must end in a state field.');
+ }
+ }
+
+ /**
+ * Validates that the given PathExpression 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 PathExpression 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
@@ -351,15 +529,21 @@ class Parser
break;
}
}
+
/**
* SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
*/
public function SelectStatement()
{
+ // We need to prevent semantical checks on SelectClause,
+ // since we do not have any IdentificationVariable yet
$this->_beginDeferredPathExpressionStack();
+
$selectClause = $this->SelectClause();
$fromClause = $this->FromClause();
+
+ // Activate semantical checks after this point. Process all deferred checks in pipeline
$this->_processDeferredPathExpressionStack();
$whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE)
@@ -379,50 +563,6 @@ class Parser
);
}
- /**
- * Begins a new stack of deferred path expressions.
- */
- private function _beginDeferredPathExpressionStack()
- {
- $this->_deferredPathExpressionStacks[] = array();
- }
-
- /**
- * Processes the topmost stack of deferred path expressions.
- * These will be validated to make sure they are indeed
- * valid StateFieldPathExpressions and additional information
- * is attached to their AST nodes.
- */
- private function _processDeferredPathExpressionStack()
- {
- $exprStack = array_pop($this->_deferredPathExpressionStacks);
- $qComps = $this->_queryComponents;
-
- foreach ($exprStack as $expr) {
- switch ($expr->getType()) {
- 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;
-
- case AST\PathExpression::TYPE_SINGLE_VALUED_PATH_EXPRESSION:
- $this->_validateSingleValuedPathExpression($expr);
- break;
-
- default:
- $this->semanticalError('Encountered invalid PathExpression.');
- break;
- }
- }
- }
-
/**
* UpdateStatement ::= UpdateClause [WhereClause]
*/
@@ -435,7 +575,210 @@ class Parser
return $updateStatement;
}
+
+ /**
+ * DeleteStatement ::= DeleteClause [WhereClause]
+ */
+ public function DeleteStatement()
+ {
+ $deleteStatement = new AST\DeleteStatement($this->DeleteClause());
+ $deleteStatement->setWhereClause(
+ $this->_lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null
+ );
+ return $deleteStatement;
+ }
+
+
+ /**
+ * IdentificationVariable ::= identifier
+ */
+ public function IdentificationVariable()
+ {
+ $this->match(Lexer::T_IDENTIFIER);
+
+ return $this->_lexer->token['value'];
+ }
+
+ /**
+ * AliasIdentificationVariable = identifier
+ */
+ public function AliasIdentificationVariable()
+ {
+ $this->match(Lexer::T_IDENTIFIER);
+
+ return $this->_lexer->token['value'];
+ }
+
+ /**
+ * AbstractSchemaName ::= identifier
+ */
+ public function AbstractSchemaName()
+ {
+ $this->match(Lexer::T_IDENTIFIER);
+
+ return $this->_lexer->token['value'];
+ }
+
+ /**
+ * ResultVariable ::= identifier
+ */
+ public function ResultVariable()
+ {
+ $this->match(Lexer::T_IDENTIFIER);
+
+ return $this->_lexer->token['value'];
+ }
+
+
+ /**
+ * JoinPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField)
+ */
+ public function JoinPathExpression()
+ {
+ $identificationVariable = $this->IdentificationVariable();
+ $this->match('.');
+ $this->match(Lexer::T_IDENTIFIER);
+
+ return new AST\JoinPathExpression(
+ $identificationVariable, $this->_lexer->token['value']
+ );
+ }
+
+ /**
+ * Parses an arbitrary path expression without any semantic validation.
+ *
+ * PathExpression ::= IdentificationVariable "." {identifier "."}* identifier
+ *
+ * @return PathExpression
+ */
+ public function PathExpression($type)
+ {
+ $identificationVariable = $this->IdentificationVariable();
+
+ $parts = array();
+
+ do {
+ $this->match('.');
+ $this->match(Lexer::T_IDENTIFIER);
+
+ $parts[] = $this->_lexer->token['value'];
+ } while ($this->_lexer->isNextToken('.'));
+
+ return new AST\PathExpression($type, $identificationVariable, $parts);
+ }
+
+ /**
+ * AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression
+ *
+ * @todo Implement this grammar rule which is used in SubselectFromClause
+ */
+ public function AssociationPathExpression()
+ {
+
+ }
+
+ /**
+ * SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression
+ */
+ public function SingleValuedPathExpression()
+ {
+ $pathExpr = $this->PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_PATH_EXPRESSION);
+
+ if ( ! empty($this->_deferredPathExpressionStacks)) {
+ $exprStack = array_pop($this->_deferredPathExpressionStacks);
+ $exprStack[] = $pathExpr;
+ array_push($this->_deferredPathExpressionStacks, $exprStack);
+
+ return $pathExpr;
+ }
+
+ $this->_validateSingleValuedPathExpression($pathExpr);
+
+ return $pathExpr;
+ }
+
+ /**
+ * StateFieldPathExpression ::= SimpleStateFieldPathExpression | SimpleStateFieldAssociationPathExpression
+ */
+ public function StateFieldPathExpression()
+ {
+ $pathExpr = $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;
+ }
+
+ /**
+ * SingleValuedAssociationPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* SingleValuedAssociationField
+ */
+ public function SingleValuedAssociationPathExpression()
+ {
+ $pathExpr = $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;
+ }
+
+ /**
+ * CollectionValuedPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* CollectionValuedAssociationField
+ */
+ public function CollectionValuedPathExpression()
+ {
+ $pathExpr = $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
+ */
+ public function SimpleStateFieldPathExpression()
+ {
+ $pathExpr = $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;
+ }
+
+
/**
* UpdateClause ::= "UPDATE" AbstractSchemaName [["AS"] AliasIdentificationVariable] "SET" UpdateItem {"," UpdateItem}*
*/
@@ -500,7 +843,9 @@ class Parser
$this->match(Lexer::T_IDENTIFIER);
$field = $this->_lexer->token['value'];
+
$this->match('=');
+
$newValue = $this->NewValue();
$updateItem = new AST\UpdateItem($field, $newValue);
@@ -536,19 +881,6 @@ class Parser
return $this->SimpleArithmeticExpression();
}
- /**
- * DeleteStatement ::= DeleteClause [WhereClause]
- */
- public function DeleteStatement()
- {
- $deleteStatement = new AST\DeleteStatement($this->DeleteClause());
- $deleteStatement->setWhereClause(
- $this->_lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null
- );
-
- return $deleteStatement;
- }
-
/**
* DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName [["AS"] AliasIdentificationVariable]
*/
@@ -641,7 +973,7 @@ class Parser
$expression = $this->IdentificationVariable();
} else if (($isFunction = $this->_isFunction()) !== false || $this->_isSubselect()) {
if ($isFunction) {
- if ($this->isAggregateFunction($this->_lexer->lookahead['type'])) {
+ if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
$expression = $this->AggregateExpression();
} else {
$expression = $this->_Function();
@@ -674,26 +1006,6 @@ class Parser
return new AST\SelectExpression($expression, $fieldAliasIdentificationVariable);
}
-
- /**
- * ResultVariable ::= identifier
- */
- public function ResultVariable()
- {
- $this->match(Lexer::T_IDENTIFIER);
-
- return $this->_lexer->token['value'];
- }
-
- /**
- * IdentificationVariable ::= identifier
- */
- public function IdentificationVariable()
- {
- $this->match(Lexer::T_IDENTIFIER);
-
- return $this->_lexer->token['value'];
- }
/**
* IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration}*
@@ -745,26 +1057,6 @@ class Parser
);
}
- /**
- * AbstractSchemaName ::= identifier
- */
- public function AbstractSchemaName()
- {
- $this->match(Lexer::T_IDENTIFIER);
-
- return $this->_lexer->token['value'];
- }
-
- /**
- * AliasIdentificationVariable = identifier
- */
- public function AliasIdentificationVariable()
- {
- $this->match(Lexer::T_IDENTIFIER);
-
- return $this->_lexer->token['value'];
- }
-
/**
* JoinVariableDeclaration ::= Join [IndexBy]
*/
@@ -778,7 +1070,7 @@ class Parser
}
/**
- * Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinAssociationPathExpression
+ * Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinPathExpression
* ["AS"] AliasIdentificationVariable [("ON" | "WITH") ConditionalExpression]
*/
public function Join()
@@ -848,20 +1140,6 @@ class Parser
return $join;
}
- /**
- * JoinPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField)
- */
- public function JoinPathExpression()
- {
- $identificationVariable = $this->IdentificationVariable();
- $this->match('.');
- $this->match(Lexer::T_IDENTIFIER);
-
- return new AST\JoinPathExpression(
- $identificationVariable, $this->_lexer->token['value']
- );
- }
-
/**
* IndexBy ::= "INDEX" "BY" SimpleStateFieldPathExpression
*/
@@ -878,236 +1156,6 @@ class Parser
return $pathExp;
}
- /**
- * StateFieldPathExpression ::= SimpleStateFieldPathExpression | SimpleStateFieldAssociationPathExpression
- */
- public function StateFieldPathExpression()
- {
- $pathExpr = $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;
- }
-
- /**
- * SimpleStateFieldPathExpression ::= IdentificationVariable "." StateField
- */
- public function SimpleStateFieldPathExpression()
- {
- $pathExpr = $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;
- }
-
- /**
- * SingleValuedAssociationPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* SingleValuedAssociationField
- */
- public function SingleValuedAssociationPathExpression()
- {
- $pathExpr = $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;
- }
-
- /**
- * CollectionValuedPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* CollectionValuedAssociationField
- */
- public function CollectionValuedPathExpression()
- {
- $pathExpr = $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;
- }
-
- /**
- * SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression
- */
- public function SingleValuedPathExpression()
- {
- $pathExpr = $this->PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_PATH_EXPRESSION);
-
- if ( ! empty($this->_deferredPathExpressionStacks)) {
- $exprStack = array_pop($this->_deferredPathExpressionStacks);
- $exprStack[] = $pathExpr;
- array_push($this->_deferredPathExpressionStacks, $exprStack);
-
- return $pathExpr;
- }
-
- $this->_validateSingleValuedPathExpression($pathExpr);
-
- return $pathExpr;
- }
-
- /**
- * Validates that the given PathExpression is a semantically correct
- * CollectionValuedPathExpression as specified in the grammar.
- *
- * @param PathExpression $pathExpression
- */
- private function _validateCollectionValuedPathExpression(AST\PathExpression $pathExpression)
- {
- $class = $this->_queryComponents[$pathExpression->getIdentificationVariable()]['metadata'];
- $collectionValuedField = null;
-
- foreach ($pathExpression->getParts() as $field) {
- if ($collectionValuedField !== null) {
- $this->semanticalError('Can not navigate through collection-valued field named ' . $collectionValuedField);
- }
-
- if ( ! isset($class->associationMappings[$field])) {
- $this->semanticalError('Class ' . $class->name . ' has no association field named ' . $lastItem);
- }
-
- if ($class->associationMappings[$field]->isOneToOne()) {
- $class = $this->_em->getClassMetadata($class->associationMappings[$field]->targetEntityName);
- } else {
- $collectionValuedField = $field;
- }
- }
-
- if ($collectionValuedField === null) {
- $this->syntaxError('SingleValuedAssociationField or CollectionValuedAssociationField');
- }
- }
-
- /**
- * Validates that the given PathExpression is a semantically correct
- * SingleValuedAssociationPathExpression as specified in the grammar.
- *
- * @param PathExpression $pathExpression
- */
- private function _validateSingleValuedAssociationPathExpression(AST\PathExpression $pathExpression)
- {
- $class = $this->_queryComponents[$pathExpression->getIdentificationVariable()]['metadata'];
-
- foreach ($pathExpression->getParts() as $field) {
- if ( ! isset($class->associationMappings[$field])) {
- $this->semanticalError('Class ' . $class->name . ' has no association field named ' . $field);
- }
-
- if ($class->associationMappings[$field]->isOneToOne()) {
- $class = $this->_em->getClassMetadata($class->associationMappings[$field]->targetEntityName);
- } else {
- $this->syntaxError('SingleValuedAssociationField');
- }
- }
- }
-
- /**
- * Validates that the given PathExpression is a semantically correct
- * StateFieldPathExpression as specified in the grammar.
- *
- * @param PathExpression $pathExpression
- */
- private function _validateStateFieldPathExpression(AST\PathExpression $pathExpression)
- {
- $stateFieldSeen = $this->_validateSingleValuedPathExpression($pathExpression);
-
- if ( ! $stateFieldSeen) {
- $this->semanticalError('Invalid StateFieldPathExpression. Must end in a state field.');
- }
- }
-
- /**
- * Validates that the given PathExpression is a semantically correct
- * SingleValuedPathExpression as specified in the grammar.
- *
- * @param PathExpression $pathExpression
- * @return boolean
- */
- private function _validateSingleValuedPathExpression(AST\PathExpression $pathExpression)
- {
- $class = $this->_queryComponents[$pathExpression->getIdentificationVariable()]['metadata'];
- $stateFieldSeen = false;
-
- foreach ($pathExpression->getParts() as $field) {
- if ($stateFieldSeen) {
- $this->semanticalError('Cannot navigate through state field.');
- }
-
- if (isset($class->associationMappings[$field]) && $class->associationMappings[$field]->isOneToOne()) {
- $class = $this->_em->getClassMetadata($class->associationMappings[$field]->targetEntityName);
- } else if (isset($class->fieldMappings[$field])) {
- $stateFieldSeen = true;
- } else {
- $this->semanticalError('SingleValuedAssociationField or StateField expected.');
- }
- }
-
- // We need to force the type in PathExpression since SingleValuedPathExpression is in a
- // state of recognition of what's is the dealed type (StateField or SingleValuedAssociation)
- $pathExpression->setType(
- ($stateFieldSeen)
- ? AST\PathExpression::TYPE_STATE_FIELD
- : AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION
- );
-
- return $stateFieldSeen;
- }
-
- /**
- * Parses an arbitrary path expression without any semantic validation.
- *
- * PathExpression ::= IdentificationVariable "." {identifier "."}* identifier
- *
- * @return PathExpression
- */
- public function PathExpression($type)
- {
- $identificationVariable = $this->IdentificationVariable();
-
- $parts = array();
-
- do {
- $this->match('.');
- $this->match(Lexer::T_IDENTIFIER);
-
- $parts[] = $this->_lexer->token['value'];
- } while ($this->_lexer->isNextToken('.'));
-
- return new AST\PathExpression($type, $identificationVariable, $parts);
- }
-
/**
* EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression
*/
@@ -1574,23 +1622,6 @@ class Parser
return isset(self::$_DATETIME_FUNCTIONS[strtolower($funcName)]);
}
- /**
- * Peeks beyond the specified token and returns the first token after that one.
- */
- private function _peekBeyond($token)
- {
- $peek = $this->_lexer->peek();
-
- while ($peek['value'] != $token) {
- $peek = $this->_lexer->peek();
- }
-
- $peek = $this->_lexer->peek();
- $this->_lexer->resetPeek();
-
- return $peek;
- }
-
/**
* Checks whether the given token is a comparison operator.
*/
@@ -1985,7 +2016,7 @@ class Parser
$peek = $this->_lexer->glimpse();
if ($peek['value'] == '(') {
- if ($this->isAggregateFunction($this->_lexer->lookahead['type'])) {
+ if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
return $this->AggregateExpression();
}
@@ -2046,16 +2077,6 @@ class Parser
return $function;
}
- /**
- * Checks whether the given token type indicates an aggregate function.
- */
- public function isAggregateFunction($tokenType)
- {
- return $tokenType == Lexer::T_AVG || $tokenType == Lexer::T_MIN ||
- $tokenType == Lexer::T_MAX || $tokenType == Lexer::T_SUM ||
- $tokenType == Lexer::T_COUNT;
- }
-
/**
* ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!="
*/
@@ -2180,22 +2201,12 @@ class Parser
$this->match(Lexer::T_INPUT_PARAMETER);
return new AST\InputParameter($this->_lexer->token['value']);
- } else if ($this->isAggregateFunction($this->_lexer->lookahead['type'])) {
+ } else if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
return $this->AggregateExpression();
}
$this->syntaxError('StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression');
}
-
- /**
- * Sets the list of custom tree walkers.
- *
- * @param array $treeWalkers
- */
- public function setSqlTreeWalker($treeWalker)
- {
- $this->_treeWalker = $treeWalker;
- }
/**
* Registers a custom function that returns strings.
diff --git a/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php b/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php
index 986af97fe..c77fc4f2f 100644
--- a/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php
+++ b/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php
@@ -47,7 +47,7 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase
}
$parser = new \Doctrine\ORM\Query\Parser($query);
- $parser->setSqlTreeWalker(new \Doctrine\Tests\Mocks\MockTreeWalker);
+ //$parser->setSqlTreeWalker(new \Doctrine\Tests\Mocks\MockTreeWalker);
return $parser->parse();
}
@@ -67,11 +67,6 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase
$this->assertValidDql('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u');
}
- public function testInvalidSelectSingleComponentWithAsterisk()
- {
- //$this->assertInvalidDql('SELECT p FROM Doctrine\Tests\Models\CMS\CmsUser u', true);
- }
-
public function testSelectSingleComponentWithMultipleColumns()
{
$this->assertValidDql('SELECT u.name, u.username FROM Doctrine\Tests\Models\CMS\CmsUser u');
@@ -247,14 +242,14 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase
{
$this->assertValidDql("SELECT u.id FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.name LIKE 'z|%' ESCAPE '|'");
}
-/*
- public function testImplicitJoinInWhereOnSingleValuedAssociationPathExpression()
+
+ /*public function testImplicitJoinInWhereOnSingleValuedAssociationPathExpression()
{
// This should be allowed because avatar is a single-value association.
// SQL: SELECT ... FROM forum_user fu INNER JOIN forum_avatar fa ON fu.avatar_id = fa.id WHERE fa.id = ?
$this->assertValidDql("SELECT u FROM Doctrine\Tests\Models\Forum\ForumUser u WHERE u.avatar.id = ?");
- }
-*/
+ }*/
+
public function testImplicitJoinInWhereOnCollectionValuedPathExpression()
{
// This should be forbidden, because articles is a collection
@@ -264,8 +259,7 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase
public function testInvalidSyntaxIsRejected()
{
$this->assertInvalidDql("FOOBAR CmsUser");
- //$this->assertInvalidDql("DELETE FROM Doctrine\Tests\Models\CMS\CmsUser.articles");
- //$this->assertInvalidDql("DELETE FROM Doctrine\Tests\Models\CMS\CmsUser cu WHERE cu.articles.id > ?");
+ $this->assertInvalidDql("DELETE FROM Doctrine\Tests\Models\CMS\CmsUser.articles");
$this->assertInvalidDql("SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u JOIN u.articles.comments");
// Currently UNDEFINED OFFSET error