1
0
mirror of synced 2025-01-31 12:32:59 +03:00

[2.0] Refactorings in DQL parser to allow more granular enhancements scheduled for later versions. Fixed issues with missing validations of invalid ResultVariable, Unknown query components and Re-declaration of query compoenents.

This commit is contained in:
guilhermeblanco 2009-12-27 03:26:15 +00:00
parent 64f59a7a49
commit 262855a66c
7 changed files with 311 additions and 154 deletions

View File

@ -59,15 +59,6 @@ class PathExpression extends Node
public function dispatch($walker) public function dispatch($walker)
{ {
switch ($this->type) { return $walker->walkPathExpression($this);
case self::TYPE_STATE_FIELD:
return $walker->walkStateFieldPathExpression($this);
case self::TYPE_SINGLE_VALUED_ASSOCIATION:
return $walker->walkSingleValuedAssociationPathExpression($this);
case self::TYPE_COLLECTION_VALUED_ASSOCIATION:
return $walker->walkCollectionValuedAssociationPathExpression($this);
default:
throw new \Exception("Unexhaustive match.");
}
} }
} }

View File

@ -66,12 +66,12 @@ class Parser
); );
/** /**
* Path expressions that were encountered during parsing of SelectExpressions * Expressions that were encountered during parsing of identifiers and expressions
* and still need to be validated. * and still need to be validated.
* *
* @var array * @var array
*/ */
private $_deferredPathExpressionStacks = array(); private $_deferredExpressionsStack = array();
/** /**
* The lexer. * The lexer.
@ -485,25 +485,172 @@ class Parser
} }
/** /**
* Begins a new stack of deferred path expressions. * Subscribe expression to be validated.
*
* @param mixed $expression
* @param string $method
* @param array $token
* @param integer $nestingLevel
*/ */
private function _beginDeferredPathExpressionStack() private function _subscribeExpression($expression, $method, $token, $nextingLevel = null)
{ {
$this->_deferredPathExpressionStacks[] = array(); $nestingLevel = ($nestingLevel !== null) ?: $this->_nestingLevel;
$exprStack[] = array(
'method' => $method,
'expression' => $expression,
'nestingLevel' => $nestingLevel,
'token' => $token,
);
array_push($this->_deferredExpressionsStack, $exprStack);
} }
/** /**
* Processes the topmost stack of deferred path expressions. * Processes the topmost stack of deferred path expressions.
*/ */
private function _processDeferredPathExpressionStack() private function _processDeferredExpressionsStack()
{ {
$exprStack = array_pop($this->_deferredPathExpressionStacks); foreach ($this->_deferredExpressionsStack as $item) {
$method = '_validate' . $item['method'];
foreach ($exprStack as $item) { $this->$method($item['expression'], $item['token'], $item['nestingLevel']);
$this->_validatePathExpression( }
$item['pathExpression'], $item['nestingLevel'], $item['token'] }
/**
* Validates that the given <tt>IdentificationVariable</tt> is a semantically correct.
* It must exist in query components list.
*
* @param string $identVariable
* @param array $token
* @param integer $nestingLevel
*
* @return array Query Component
*/
private function _validateIdentificationVariable($identVariable, $token, $nestingLevel)
{
// Check if IdentificationVariable exists in queryComponents
if ( ! isset($this->_queryComponents[$identVariable])) {
$this->semanticalError("'$identVariable' is not defined.", $token);
}
$qComp = $this->_queryComponents[$identVariable];
// Check if queryComponent points to an AbstractSchemaName or a ResultVariable
if ( ! isset($qComp['metadata'])) {
$this->semanticalError("'$identVariable' does not point to a Class.", $token);
}
// Validate if identification variable nesting level is lower or equal than the current one
if ($qComp['nestingLevel'] > $nestingLevel) {
$this->semanticalError(
"'$identVariable' is used outside the scope of its declaration.", $token
); );
} }
return $qComp;
}
/**
* Validates that the given <tt>AliasIdentificationVariable</tt> is a semantically correct.
* It must not exist in query components list.
*
* @param string $aliasIdentVariable
* @param array $token
* @param integer $nestingLevel
*
* @return boolean
*/
private function _validateAliasIdentificationVariable($aliasIdentVariable, $token, $nestingLevel)
{
$exists = isset($this->_queryComponents[$aliasIdentVariable]);
if ($exists) {
$this->semanticalError("'$aliasIdentVariable' is already defined.", $token);
}
return $exists;
}
/**
* Validates that the given <tt>AbstractSchemaName</tt> is a semantically correct.
* It must be defined in the scope of Application.
*
* @param string $schemaName
* @param array $token
* @param integer $nestingLevel
*
* @return boolean
*/
private function _validateAbstractSchemaName($schemaName, $token, $nestingLevel)
{
$exists = class_exists($schemaName, true);
if ( ! $exists) {
$this->semanticalError("Class '$schemaName' is not defined.", $token);
}
return $exists;
}
/**
* Validates that the given <tt>AliasIdentificationVariable</tt> is a semantically correct.
* It must exist in query components list.
*
* @param string $resultVariable
* @param array $token
* @param integer $nestingLevel
*
* @return array Query Component
*/
private function _validateResultVariable($resultVariable, $token, $nestingLevel)
{
// Check if ResultVariable exists in queryComponents
if ( ! isset($this->_queryComponents[$resultVariable])) {
$this->semanticalError("'$resultVariable' is not defined.", $token);
}
$qComp = $this->_queryComponents[$resultVariable];
// Check if queryComponent points to an AbstractSchemaName or a ResultVariable
if ( ! isset($qComp['resultVariable'])) {
$this->semanticalError("'$identVariable' does not point to a ResultVariable.", $token);
}
// Validate if identification variable nesting level is lower or equal than the current one
if ($qComp['nestingLevel'] > $nestingLevel) {
$this->semanticalError(
"'$resultVariable' is used outside the scope of its declaration.", $token
);
}
return $qComp;
}
/**
* Validates that the given <tt>JoinAssociationPathExpression</tt> is a semantically correct.
*
* @param JoinAssociationPathExpression $pathExpression
* @param array $token
* @param integer $nestingLevel
*
* @return array Query Component
*/
private function _validateJoinAssociationPathExpression(AST\JoinAssociationPathExpression $pathExpression, $token, $nestingLevel)
{
$qComp = $this->_queryComponents[$pathExpression->identificationVariable];;
// Validating association field (*-to-one or *-to-many)
$field = $pathExpression->associationField;
$class = $qComp['metadata'];
if ( ! isset($class->associationMappings[$field])) {
$this->semanticalError('Class ' . $class->name . ' has no association named ' . $field);
}
return $qComp;
} }
/** /**
@ -516,19 +663,16 @@ class Parser
* CollectionValuedPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* CollectionValuedAssociationField * CollectionValuedPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* CollectionValuedAssociationField
* *
* @param PathExpression $pathExpression * @param PathExpression $pathExpression
* @param integer $nestingLevel
* @param array $token * @param array $token
* @param integer $nestingLevel
*
* @return integer * @return integer
*/ */
private function _validatePathExpression(AST\PathExpression $pathExpression, $nestingLevel = null, $token = null) private function _validatePathExpression(AST\PathExpression $pathExpression, $token, $nestingLevel)
{ {
$identVariable = $pathExpression->identificationVariable; $qComp = $this->_queryComponents[$pathExpression->identificationVariable];
$nestingLevel = ($nestingLevel !== null) ?: $this->_nestingLevel;
$token = ($token) ?: $this->_lexer->lookahead;
$this->_validateIdentificationVariable($identVariable, $nestingLevel, $token); $class = $qComp['metadata'];
$class = $this->_queryComponents[$identVariable]['metadata'];
$stateField = $collectionField = null; $stateField = $collectionField = null;
foreach ($pathExpression->parts as $field) { foreach ($pathExpression->parts as $field) {
@ -550,7 +694,7 @@ class Parser
// Check if field exists // Check if field exists
if ( ! isset($class->associationMappings[$field]) && ! isset($class->fieldMappings[$field])) { if ( ! isset($class->associationMappings[$field]) && ! isset($class->fieldMappings[$field])) {
$this->semanticalError( $this->semanticalError(
'Class ' . $class->name . ' has no field named ' . $field, $token 'Class ' . $class->name . ' has no field or association named ' . $field, $token
); );
} }
@ -597,12 +741,12 @@ class Parser
} }
// Build the error message // Build the error message
$semanticalError = 'Invalid PathExpression.'; $semanticalError = 'Invalid PathExpression. ';
if (count($expectedStringTypes) == 1) { if (count($expectedStringTypes) == 1) {
$semanticalError .= ' Must be a ' . $expectedStringTypes[0] . '.'; $semanticalError .= 'Must be a ' . $expectedStringTypes[0] . '.';
} else { } else {
$semanticalError .= ' ' . implode(' or ', $expectedStringTypes) . ' expected.'; $semanticalError .= implode(' or ', $expectedStringTypes) . ' expected.';
} }
$this->semanticalError($semanticalError, $token); $this->semanticalError($semanticalError, $token);
@ -614,35 +758,6 @@ class Parser
return $expressionType; return $expressionType;
} }
/**
* Validates that the given <tt>IdentificationVariable</tt> is a semantically correct.
* It must exist in query components list.
*
* @param string $identVariable
* @param integer $nestingLevel
* @param array $token
* @return array Query Component
*/
private function _validateIdentificationVariable($identVariable, $nestingLevel = null, $token = null)
{
$nestingLevel = ($nestingLevel !== null) ?: $this->_nestingLevel;
$token = ($token) ?: $this->_lexer->lookahead;
if ( ! isset($this->_queryComponents[$identVariable])) {
$this->semanticalError("'$identVariable' is not defined", $token);
}
// Validate if identification variable nesting level is lower or equal than the current one
if ($this->_queryComponents[$identVariable]['nestingLevel'] > $nestingLevel) {
$this->semanticalError(
"'$idVariable' is used outside the scope of its declaration",
$token
);
}
return $this->_queryComponents[$identVariable];
}
/** /**
* QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement * QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement
@ -679,15 +794,8 @@ class Parser
*/ */
public function SelectStatement() public function SelectStatement()
{ {
// We need to prevent semantical checks on SelectClause,
// since we do not have any IdentificationVariable yet
$this->_beginDeferredPathExpressionStack();
$selectStatement = new AST\SelectStatement($this->SelectClause(), $this->FromClause()); $selectStatement = new AST\SelectStatement($this->SelectClause(), $this->FromClause());
// Activate semantical checks after this point. Process all deferred checks in pipeline
$this->_processDeferredPathExpressionStack();
$selectStatement->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE) $selectStatement->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE)
? $this->WhereClause() : null; ? $this->WhereClause() : null;
@ -700,6 +808,9 @@ class Parser
$selectStatement->orderByClause = $this->_lexer->isNextToken(Lexer::T_ORDER) $selectStatement->orderByClause = $this->_lexer->isNextToken(Lexer::T_ORDER)
? $this->OrderByClause() : null; ? $this->OrderByClause() : null;
// Activate semantical checks after this point. Process all deferred checks in pipeline
$this->_processDeferredExpressionsStack();
return $selectStatement; return $selectStatement;
} }
@ -714,6 +825,9 @@ class Parser
$updateStatement->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE) $updateStatement->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE)
? $this->WhereClause() : null; ? $this->WhereClause() : null;
// Activate semantical checks after this point. Process all deferred checks in pipeline
$this->_processDeferredExpressionsStack();
return $updateStatement; return $updateStatement;
} }
@ -728,6 +842,9 @@ class Parser
$deleteStatement->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE) $deleteStatement->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE)
? $this->WhereClause() : null; ? $this->WhereClause() : null;
// Activate semantical checks after this point. Process all deferred checks in pipeline
$this->_processDeferredExpressionsStack();
return $deleteStatement; return $deleteStatement;
} }
@ -741,7 +858,19 @@ class Parser
{ {
$this->match(Lexer::T_IDENTIFIER); $this->match(Lexer::T_IDENTIFIER);
return $this->_lexer->token['value']; $identVariable = $this->_lexer->token['value'];
// Defer IdentificationVariable validation
$exprStack = array(
'method' => 'IdentificationVariable',
'expression' => $identVariable,
'nestingLevel' => $this->_nestingLevel,
'token' => $this->_lexer->token,
);
array_push($this->_deferredExpressionsStack, $exprStack);
return $identVariable;
} }
/** /**
@ -753,7 +882,14 @@ class Parser
{ {
$this->match(Lexer::T_IDENTIFIER); $this->match(Lexer::T_IDENTIFIER);
return $this->_lexer->token['value']; $aliasIdentVariable = $this->_lexer->token['value'];
// Apply AliasIdentificationVariable validation
$this->_validateAliasIdentificationVariable(
$aliasIdentVariable, $this->_lexer->token, $this->_nestingLevel
);
return $aliasIdentVariable;
} }
/** /**
@ -765,7 +901,14 @@ class Parser
{ {
$this->match(Lexer::T_IDENTIFIER); $this->match(Lexer::T_IDENTIFIER);
return $this->_lexer->token['value']; $schemaName = $this->_lexer->token['value'];
// Apply AbstractSchemaName validation
$this->_validateAbstractSchemaName(
$schemaName, $this->_lexer->token, $this->_nestingLevel
);
return $schemaName;
} }
/** /**
@ -777,7 +920,19 @@ class Parser
{ {
$this->match(Lexer::T_IDENTIFIER); $this->match(Lexer::T_IDENTIFIER);
return $this->_lexer->token['value']; $resultVariable = $this->_lexer->token['value'];
// Defer ResultVariable validation
$exprStack = array(
'method' => 'ResultVariable',
'expression' => $resultVariable,
'nestingLevel' => $this->_nestingLevel,
'token' => $this->_lexer->token,
);
array_push($this->_deferredExpressionsStack, $exprStack);
return $resultVariable;
} }
@ -789,34 +944,37 @@ class Parser
public function JoinAssociationPathExpression() public function JoinAssociationPathExpression()
{ {
$token = $this->_lexer->lookahead; $token = $this->_lexer->lookahead;
$identVariable = $this->IdentificationVariable(); $identVariable = $this->IdentificationVariable();
$this->match(Lexer::T_DOT); $this->match(Lexer::T_DOT);
$this->match(Lexer::T_IDENTIFIER); $this->match(Lexer::T_IDENTIFIER);
$field = $this->_lexer->token['value']; $field = $this->_lexer->token['value'];
// Validating IdentificationVariable (it was already defined previously) $pathExpr = new AST\JoinAssociationPathExpression($identVariable, $field);
$this->_validateIdentificationVariable($identVariable, null, $token);
// Validating association field (*-to-one or *-to-many) // Defer JoinAssociationPathExpression validation
$class = $this->_queryComponents[$identVariable]['metadata']; $exprStack = array(
'method' => 'JoinAssociationPathExpression',
'expression' => $pathExpr,
'nestingLevel' => $this->_nestingLevel,
'token' => $token,
);
if ( ! isset($class->associationMappings[$field])) { array_push($this->_deferredExpressionsStack, $exprStack);
$this->semanticalError('Class ' . $class->name . ' has no field named ' . $field);
}
return new AST\JoinAssociationPathExpression($identVariable, $field); return $pathExpr;
} }
/** /**
* Parses an arbitrary path expression. Applies or defer semantical validation * Parses an arbitrary path expression and defers semantical validation
* based on expected types. * based on expected types.
* *
* PathExpression ::= IdentificationVariable "." {identifier "."}* identifier * PathExpression ::= IdentificationVariable "." {identifier "."}* identifier
* *
* @param integer $expectedType * @param integer $expectedTypes
* @return \Doctrine\ORM\Query\AST\PathExpression * @return \Doctrine\ORM\Query\AST\PathExpression
*/ */
public function PathExpression($expectedType) public function PathExpression($expectedTypes)
{ {
$token = $this->_lexer->lookahead; $token = $this->_lexer->lookahead;
$identVariable = $this->IdentificationVariable(); $identVariable = $this->IdentificationVariable();
@ -830,23 +988,17 @@ class Parser
} while ($this->_lexer->isNextToken(Lexer::T_DOT)); } while ($this->_lexer->isNextToken(Lexer::T_DOT));
// Creating AST node // Creating AST node
$pathExpr = new AST\PathExpression($expectedType, $identVariable, $parts); $pathExpr = new AST\PathExpression($expectedTypes, $identVariable, $parts);
// Defer PathExpression validation if requested to be defered // Defer PathExpression validation if requested to be defered
if ( ! empty($this->_deferredPathExpressionStacks)) { $exprStack = array(
$exprStack = array_pop($this->_deferredPathExpressionStacks); 'method' => 'PathExpression',
$exprStack[] = array( 'expression' => $pathExpr,
'pathExpression' => $pathExpr, 'nestingLevel' => $this->_nestingLevel,
'nestingLevel' => $this->_nestingLevel, 'token' => $token,
'token' => $token, );
);
array_push($this->_deferredPathExpressionStacks, $exprStack);
return $pathExpr; array_push($this->_deferredExpressionsStack, $exprStack);
}
// Apply PathExpression validation normally (not in defer mode)
$this->_validatePathExpression($pathExpr, $this->_nestingLevel, $token);
return $pathExpr; return $pathExpr;
} }
@ -975,7 +1127,7 @@ class Parser
} }
/** /**
* UpdateClause ::= "UPDATE" AbstractSchemaName [["AS"] AliasIdentificationVariable] "SET" UpdateItem {"," UpdateItem}* * UpdateClause ::= "UPDATE" AbstractSchemaName ["AS"] AliasIdentificationVariable "SET" UpdateItem {"," UpdateItem}*
* *
* @return \Doctrine\ORM\Query\AST\UpdateClause * @return \Doctrine\ORM\Query\AST\UpdateClause
*/ */
@ -984,18 +1136,12 @@ class Parser
$this->match(Lexer::T_UPDATE); $this->match(Lexer::T_UPDATE);
$token = $this->_lexer->lookahead; $token = $this->_lexer->lookahead;
$abstractSchemaName = $this->AbstractSchemaName(); $abstractSchemaName = $this->AbstractSchemaName();
$aliasIdentificationVariable = null;
if ($this->_lexer->isNextToken(Lexer::T_AS)) { if ($this->_lexer->isNextToken(Lexer::T_AS)) {
$this->match(Lexer::T_AS); $this->match(Lexer::T_AS);
} }
if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) { $aliasIdentificationVariable = $this->AliasIdentificationVariable();
$token = $this->_lexer->lookahead;
$aliasIdentificationVariable = $this->AliasIdentificationVariable();
} else {
$aliasIdentificationVariable = $abstractSchemaName;
}
$class = $this->_em->getClassMetadata($abstractSchemaName); $class = $this->_em->getClassMetadata($abstractSchemaName);
@ -1011,6 +1157,7 @@ class Parser
$this->_queryComponents[$aliasIdentificationVariable] = $queryComponent; $this->_queryComponents[$aliasIdentificationVariable] = $queryComponent;
$this->match(Lexer::T_SET); $this->match(Lexer::T_SET);
$updateItems = array(); $updateItems = array();
$updateItems[] = $this->UpdateItem(); $updateItems[] = $this->UpdateItem();
@ -1026,7 +1173,7 @@ class Parser
} }
/** /**
* DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName [["AS"] AliasIdentificationVariable] * DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName ["AS"] AliasIdentificationVariable
* *
* @return \Doctrine\ORM\Query\AST\DeleteClause * @return \Doctrine\ORM\Query\AST\DeleteClause
*/ */
@ -1040,18 +1187,12 @@ class Parser
$token = $this->_lexer->lookahead; $token = $this->_lexer->lookahead;
$deleteClause = new AST\DeleteClause($this->AbstractSchemaName()); $deleteClause = new AST\DeleteClause($this->AbstractSchemaName());
$aliasIdentificationVariable = null;
if ($this->_lexer->isNextToken(Lexer::T_AS)) { if ($this->_lexer->isNextToken(Lexer::T_AS)) {
$this->match(Lexer::T_AS); $this->match(Lexer::T_AS);
} }
if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) { $aliasIdentificationVariable = $this->AliasIdentificationVariable();
$token = $this->_lexer->lookahead;
$aliasIdentificationVariable = $this->AliasIdentificationVariable();
} else {
$aliasIdentificationVariable = $deleteClause->abstractSchemaName;
}
$deleteClause->aliasIdentificationVariable = $aliasIdentificationVariable; $deleteClause->aliasIdentificationVariable = $aliasIdentificationVariable;
$class = $this->_em->getClassMetadata($deleteClause->abstractSchemaName); $class = $this->_em->getClassMetadata($deleteClause->abstractSchemaName);
@ -1183,12 +1324,8 @@ class Parser
// Increase query nesting level // Increase query nesting level
$this->_nestingLevel++; $this->_nestingLevel++;
$this->_beginDeferredPathExpressionStack();
$subselect = new AST\Subselect($this->SimpleSelectClause(), $this->SubselectFromClause()); $subselect = new AST\Subselect($this->SimpleSelectClause(), $this->SubselectFromClause());
$this->_processDeferredPathExpressionStack();
$subselect->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE) $subselect->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE)
? $this->WhereClause() : null; ? $this->WhereClause() : null;
@ -1216,17 +1353,14 @@ class Parser
public function UpdateItem() public function UpdateItem()
{ {
$token = $this->_lexer->lookahead; $token = $this->_lexer->lookahead;
$identVariable = $this->IdentificationVariable(); $identVariable = $this->IdentificationVariable();
// Validate if IdentificationVariable is defined
$queryComponent = $this->_validateIdentificationVariable($identVariable, null, $token);
$this->match(Lexer::T_DOT); $this->match(Lexer::T_DOT);
$this->match(Lexer::T_IDENTIFIER); $this->match(Lexer::T_IDENTIFIER);
$field = $this->_lexer->token['value']; $field = $this->_lexer->token['value'];
// Check if field exists // Check if field exists
$class = $queryComponent['metadata']; $class = $this->_queryComponents[$identVariable]['metadata'];
if ( ! isset($class->associationMappings[$field]) && ! isset($class->fieldMappings[$field])) { if ( ! isset($class->associationMappings[$field]) && ! isset($class->fieldMappings[$field])) {
$this->semanticalError( $this->semanticalError(
@ -1285,17 +1419,6 @@ class Parser
if ($glimpse['value'] != '.') { if ($glimpse['value'] != '.') {
$token = $this->_lexer->lookahead; $token = $this->_lexer->lookahead;
$expr = $this->ResultVariable(); $expr = $this->ResultVariable();
// Check if ResultVariable is defined in query components
$queryComponent = $this->_validateIdentificationVariable($expr, null, $token);
// Outer defininition used in inner subselect is not enough.
// ResultVariable exists in queryComponents, check nesting level
if ($queryComponent['nestingLevel'] != $this->_nestingLevel) {
$this->semanticalError(
"'$expr' is used outside the scope of its declaration"
);
}
} else { } else {
$expr = $this->StateFieldPathExpression(); $expr = $this->StateFieldPathExpression();
} }
@ -1564,7 +1687,7 @@ class Parser
// Include ResultVariable in query components. // Include ResultVariable in query components.
$this->_queryComponents[$fieldAliasIdentificationVariable] = array( $this->_queryComponents[$fieldAliasIdentificationVariable] = array(
'resultvariable' => $expression, 'resultVariable' => $expression,
'nestingLevel' => $this->_nestingLevel, 'nestingLevel' => $this->_nestingLevel,
'token' => $token, 'token' => $token,
); );

View File

@ -21,6 +21,8 @@
namespace Doctrine\ORM\Query; namespace Doctrine\ORM\Query;
use Doctrine\ORM\Query\AST\PathExpression;
/** /**
* Description of QueryException * Description of QueryException
* *
@ -64,6 +66,14 @@ class QueryException extends \Doctrine\Common\DoctrineException
return new self("Invalid parameter: token ".$key." is not defined in the query."); return new self("Invalid parameter: token ".$key." is not defined in the query.");
} }
public static function invalidPathExpression($pathExpr)
{
return new self(
"Invalid PathExpression '" . $pathExpr->identificationVariable .
"." . implode('.', $pathExpr->parts) . "'."
);
}
/** /**
* @param Doctrine\ORM\Mapping\AssociationMapping $assoc * @param Doctrine\ORM\Mapping\AssociationMapping $assoc
*/ */

View File

@ -22,6 +22,7 @@
namespace Doctrine\ORM\Query; namespace Doctrine\ORM\Query;
use Doctrine\ORM\Query, use Doctrine\ORM\Query,
Doctrine\ORM\Query\QueryException,
Doctrine\Common\DoctrineException; Doctrine\Common\DoctrineException;
/** /**
@ -426,11 +427,34 @@ class SqlWalker implements TreeWalker
$sql .= $class->getQuotedColumnName($fieldName, $this->_platform); $sql .= $class->getQuotedColumnName($fieldName, $this->_platform);
break; break;
case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION:
// "u.Group" should be converted to:
// 1- IdentificationVariable is the owning side:
// Just append the condition: u.group_id = ?
/*$parts = $pathExpr->parts;
$numParts = count($parts);
$dqlAlias = $pathExpr->identificationVariable;
$fieldName = $parts[$numParts - 1];
$qComp = $this->_queryComponents[$dqlAlias];
$class = $qComp['metadata'];
$assoc = $class->associationMappings[$fieldName];
if ($assoc->isOwningSide) {
foreach ($assoc->)
$sql .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.';
}
// 2- IdentificationVariable is the inverse side:
// Join required: INNER JOIN u.Group g
// Append condition: g.id = ?
break;*/
case AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION: case AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION:
throw DoctrineException::notImplemented(); throw DoctrineException::notImplemented();
default: default:
throw DoctrineException::invalidPathExpression($pathExpr->type); throw QueryException::invalidPathExpression($pathExpr);
} }
return $sql; return $sql;

View File

@ -52,10 +52,6 @@ class DeleteSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
parent::assertEquals($sqlToBeConfirmed, $query->getSql()); parent::assertEquals($sqlToBeConfirmed, $query->getSql());
$query->free(); $query->free();
} catch (\Exception $e) { } catch (\Exception $e) {
if ($debug) {
echo $e->getTraceAsString() . PHP_EOL;
}
$this->fail($e->getMessage()); $this->fail($e->getMessage());
} }
} }

View File

@ -1,7 +1,8 @@
<?php <?php
namespace Doctrine\Tests\ORM\Query; namespace Doctrine\Tests\ORM\Query;
use Doctrine\ORM\Query; use Doctrine\ORM\Query,
Doctrine\ORM\Query\QueryException;
require_once __DIR__ . '/../../TestInit.php'; require_once __DIR__ . '/../../TestInit.php';
@ -18,7 +19,7 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase
{ {
try { try {
$parserResult = $this->parseDql($dql); $parserResult = $this->parseDql($dql);
} catch (\Exception $e) { } catch (QueryException $e) {
if ($debug) { if ($debug) {
echo $e->getTraceAsString() . PHP_EOL; echo $e->getTraceAsString() . PHP_EOL;
} }
@ -31,8 +32,9 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase
{ {
try { try {
$parserResult = $this->parseDql($dql); $parserResult = $this->parseDql($dql);
$this->fail('No syntax errors were detected, when syntax errors were expected'); $this->fail('No syntax errors were detected, when syntax errors were expected');
} catch (\Exception $e) { } catch (QueryException $e) {
if ($debug) { if ($debug) {
echo $e->getMessage() . PHP_EOL; echo $e->getMessage() . PHP_EOL;
echo $e->getTraceAsString() . PHP_EOL; echo $e->getTraceAsString() . PHP_EOL;
@ -248,6 +250,16 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase
$this->assertValidDql("SELECT u.id FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.name LIKE 'z|%' ESCAPE '|'"); $this->assertValidDql("SELECT u.id FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.name LIKE 'z|%' ESCAPE '|'");
} }
public function testFieldComparisonWithoutAlias()
{
$this->assertInvalidDql("SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE id = 1");
}
public function testDuplicatedAliasDeclaration()
{
$this->assertInvalidDql("SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u INNER JOIN u.articles u WHERE u.id = 1");
}
/*public function testImplicitJoinInWhereOnSingleValuedAssociationPathExpression() /*public function testImplicitJoinInWhereOnSingleValuedAssociationPathExpression()
{ {
// This should be allowed because avatar is a single-value association. // This should be allowed because avatar is a single-value association.
@ -288,7 +300,7 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase
public function testDeleteAll() public function testDeleteAll()
{ {
$this->assertValidDql('DELETE FROM Doctrine\Tests\Models\CMS\CmsUser'); $this->assertValidDql('DELETE FROM Doctrine\Tests\Models\CMS\CmsUser u');
} }
public function testDeleteWithCondition() public function testDeleteWithCondition()

View File

@ -464,16 +464,17 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
} }
/* Not yet implemented, needs more thought /* Not yet implemented, needs more thought */
public function testSingleValuedAssociationFieldInWhere() public function testSingleValuedAssociationFieldInWhere()
{ {
$this->assertSqlGeneration( /*$this->assertSqlGeneration(
"SELECT p FROM Doctrine\Tests\Models\CMS\CmsPhonenumber p WHERE p.user = ?1", "SELECT p FROM Doctrine\Tests\Models\CMS\CmsPhonenumber p WHERE p.user = ?1",
"SELECT c0_.phonenumber AS phonenumber0 FROM cms_phonenumbers c0_ WHERE c0_.user_id = ?" "SELECT c0_.id AS id0, c0_user_id AS user_id1, c0_.phonenumber AS phonenumber2 FROM cms_phonenumbers c0_ WHERE c0_.user_id = ?"
); );
$this->assertSqlGeneration( $this->assertSqlGeneration(
"SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.address = ?1", "SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.address = ?1",
"SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE c0_.id = (SELECT c1_.user_id FROM cms_addresses c1_ WHERE c1_.id = ?)" //"SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE c0_.id = (SELECT c1_.user_id FROM cms_addresses c1_ WHERE c1_.id = ?)"
); "SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE EXISTS (SELECT 1 FROM cms_addresses c1_ WHERE c1_.user_id = c0_.id AND c1_.id = ?)"
}*/ );*/
}
} }