Commit b3d110ba authored by guilhermeblanco's avatar guilhermeblanco

[2.0] Started massive reorganization of grammar rules in DQL parser.

parent a3018340
......@@ -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;
}
private function _peekBeyond($token)
{
$peek = $this->_lexer->peek();
while ($peek['value'] != $token) {
$peek = $this->_lexer->peek();
}
$peek = $this->_lexer->peek();
$this->_lexer->resetPeek();
$this->_errorDistance = 0;
}*/
return $peek;
}
/**
* Gets the EntityManager used by the parser.
* Checks if the next-next (after lookahead) token starts a function.
*
* @return EntityManager
* @return boolean TRUE if the next-next tokens start a function, FALSE otherwise.
*/
public function getEntityManager()
private function _isFunction()
{
return $this->_em;
$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 if the next-next (after lookahead) token starts a function.
* Checks whether the given token type indicates an aggregate function.
*
* @return boolean
* @return boolean TRUE if the token type is an aggregate function, FALSE otherwise.
*/
private function _isFunction()
private function _isAggregateFunction($tokenType)
{
$next = $this->_lexer->glimpse();
return $next['value'] === '(';
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 <tt>PathExpression</tt> 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 <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
* 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 <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
......@@ -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)
......@@ -380,62 +564,221 @@ class Parser
}
/**
* Begins a new stack of deferred path expressions.
* UpdateStatement ::= UpdateClause [WhereClause]
*/
private function _beginDeferredPathExpressionStack()
public function UpdateStatement()
{
$this->_deferredPathExpressionStacks[] = array();
$updateStatement = new AST\UpdateStatement($this->UpdateClause());
$updateStatement->setWhereClause(
$this->_lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null
);
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;
}
/**
* Processes the topmost stack of deferred path expressions.
* These will be validated to make sure they are indeed
* valid <tt>StateFieldPathExpression</tt>s and additional information
* is attached to their AST nodes.
* IdentificationVariable ::= identifier
*/
private function _processDeferredPathExpressionStack()
public function IdentificationVariable()
{
$exprStack = array_pop($this->_deferredPathExpressionStacks);
$qComps = $this->_queryComponents;
$this->match(Lexer::T_IDENTIFIER);
foreach ($exprStack as $expr) {
switch ($expr->getType()) {
case AST\PathExpression::TYPE_STATE_FIELD:
$this->_validateStateFieldPathExpression($expr);
break;
return $this->_lexer->token['value'];
}
/**
* AliasIdentificationVariable = identifier
*/
public function AliasIdentificationVariable()
{
$this->match(Lexer::T_IDENTIFIER);
case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION:
$this->_validateSingleValuedAssociationPathExpression($expr);
break;
return $this->_lexer->token['value'];
}
/**
* AbstractSchemaName ::= identifier
*/
public function AbstractSchemaName()
{
$this->match(Lexer::T_IDENTIFIER);
case AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION:
$this->_validateCollectionValuedAssociationPathExpression($expr);
break;
case AST\PathExpression::TYPE_SINGLE_VALUED_PATH_EXPRESSION:
$this->_validateSingleValuedPathExpression($expr);
break;
return $this->_lexer->token['value'];
}
/**
* ResultVariable ::= identifier
*/
public function ResultVariable()
{
$this->match(Lexer::T_IDENTIFIER);
return $this->_lexer->token['value'];
}
default:
$this->semanticalError('Encountered invalid PathExpression.');
break;
}
/**
* 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;
}
/**
* UpdateStatement ::= UpdateClause [WhereClause]
* SingleValuedAssociationPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* SingleValuedAssociationField
*/
public function UpdateStatement()
public function SingleValuedAssociationPathExpression()
{
$updateStatement = new AST\UpdateStatement($this->UpdateClause());
$updateStatement->setWhereClause(
$this->_lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null
);
$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 $updateStatement;
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}*
......@@ -734,35 +1046,15 @@ class Parser
// Building queryComponent
$queryComponent = array(
'metadata' => $classMetadata,
'parent' => null,
'relation' => null,
'map' => null
);
$this->_queryComponents[$aliasIdentificationVariable] = $queryComponent;
return new AST\RangeVariableDeclaration(
$classMetadata, $aliasIdentificationVariable
);
}
/**
* 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);
'parent' => null,
'relation' => null,
'map' => null
);
$this->_queryComponents[$aliasIdentificationVariable] = $queryComponent;
return $this->_lexer->token['value'];
return new AST\RangeVariableDeclaration(
$classMetadata, $aliasIdentificationVariable
);
}
/**
......@@ -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 <tt>PathExpression</tt> 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 <tt>PathExpression</tt> 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 <tt>PathExpression</tt> 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 <tt>PathExpression</tt> 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.
......
......@@ -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
......
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