[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:
parent
64f59a7a49
commit
262855a66c
@ -59,15 +59,6 @@ class PathExpression extends Node
|
||||
|
||||
public function dispatch($walker)
|
||||
{
|
||||
switch ($this->type) {
|
||||
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.");
|
||||
}
|
||||
return $walker->walkPathExpression($this);
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $_deferredPathExpressionStacks = array();
|
||||
private $_deferredExpressionsStack = array();
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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->_validatePathExpression(
|
||||
$item['pathExpression'], $item['nestingLevel'], $item['token']
|
||||
$this->$method($item['expression'], $item['token'], $item['nestingLevel']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @param PathExpression $pathExpression
|
||||
* @param integer $nestingLevel
|
||||
* @param array $token
|
||||
* @param integer $nestingLevel
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
private function _validatePathExpression(AST\PathExpression $pathExpression, $nestingLevel = null, $token = null)
|
||||
private function _validatePathExpression(AST\PathExpression $pathExpression, $token, $nestingLevel)
|
||||
{
|
||||
$identVariable = $pathExpression->identificationVariable;
|
||||
$nestingLevel = ($nestingLevel !== null) ?: $this->_nestingLevel;
|
||||
$token = ($token) ?: $this->_lexer->lookahead;
|
||||
$qComp = $this->_queryComponents[$pathExpression->identificationVariable];
|
||||
|
||||
$this->_validateIdentificationVariable($identVariable, $nestingLevel, $token);
|
||||
|
||||
$class = $this->_queryComponents[$identVariable]['metadata'];
|
||||
$class = $qComp['metadata'];
|
||||
$stateField = $collectionField = null;
|
||||
|
||||
foreach ($pathExpression->parts as $field) {
|
||||
@ -550,7 +694,7 @@ class Parser
|
||||
// Check if field exists
|
||||
if ( ! isset($class->associationMappings[$field]) && ! isset($class->fieldMappings[$field])) {
|
||||
$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
|
||||
$semanticalError = 'Invalid PathExpression.';
|
||||
$semanticalError = 'Invalid PathExpression. ';
|
||||
|
||||
if (count($expectedStringTypes) == 1) {
|
||||
$semanticalError .= ' Must be a ' . $expectedStringTypes[0] . '.';
|
||||
$semanticalError .= 'Must be a ' . $expectedStringTypes[0] . '.';
|
||||
} else {
|
||||
$semanticalError .= ' ' . implode(' or ', $expectedStringTypes) . ' expected.';
|
||||
$semanticalError .= implode(' or ', $expectedStringTypes) . ' expected.';
|
||||
}
|
||||
|
||||
$this->semanticalError($semanticalError, $token);
|
||||
@ -614,35 +758,6 @@ class Parser
|
||||
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
|
||||
@ -679,15 +794,8 @@ class Parser
|
||||
*/
|
||||
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());
|
||||
|
||||
// Activate semantical checks after this point. Process all deferred checks in pipeline
|
||||
$this->_processDeferredPathExpressionStack();
|
||||
|
||||
$selectStatement->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE)
|
||||
? $this->WhereClause() : null;
|
||||
|
||||
@ -700,6 +808,9 @@ class Parser
|
||||
$selectStatement->orderByClause = $this->_lexer->isNextToken(Lexer::T_ORDER)
|
||||
? $this->OrderByClause() : null;
|
||||
|
||||
// Activate semantical checks after this point. Process all deferred checks in pipeline
|
||||
$this->_processDeferredExpressionsStack();
|
||||
|
||||
return $selectStatement;
|
||||
}
|
||||
|
||||
@ -714,6 +825,9 @@ class Parser
|
||||
$updateStatement->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE)
|
||||
? $this->WhereClause() : null;
|
||||
|
||||
// Activate semantical checks after this point. Process all deferred checks in pipeline
|
||||
$this->_processDeferredExpressionsStack();
|
||||
|
||||
return $updateStatement;
|
||||
}
|
||||
|
||||
@ -728,6 +842,9 @@ class Parser
|
||||
$deleteStatement->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE)
|
||||
? $this->WhereClause() : null;
|
||||
|
||||
// Activate semantical checks after this point. Process all deferred checks in pipeline
|
||||
$this->_processDeferredExpressionsStack();
|
||||
|
||||
return $deleteStatement;
|
||||
}
|
||||
|
||||
@ -741,7 +858,19 @@ class Parser
|
||||
{
|
||||
$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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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()
|
||||
{
|
||||
$token = $this->_lexer->lookahead;
|
||||
|
||||
$identVariable = $this->IdentificationVariable();
|
||||
$this->match(Lexer::T_DOT);
|
||||
$this->match(Lexer::T_IDENTIFIER);
|
||||
$field = $this->_lexer->token['value'];
|
||||
|
||||
// Validating IdentificationVariable (it was already defined previously)
|
||||
$this->_validateIdentificationVariable($identVariable, null, $token);
|
||||
$pathExpr = new AST\JoinAssociationPathExpression($identVariable, $field);
|
||||
|
||||
// Validating association field (*-to-one or *-to-many)
|
||||
$class = $this->_queryComponents[$identVariable]['metadata'];
|
||||
// Defer JoinAssociationPathExpression validation
|
||||
$exprStack = array(
|
||||
'method' => 'JoinAssociationPathExpression',
|
||||
'expression' => $pathExpr,
|
||||
'nestingLevel' => $this->_nestingLevel,
|
||||
'token' => $token,
|
||||
);
|
||||
|
||||
if ( ! isset($class->associationMappings[$field])) {
|
||||
$this->semanticalError('Class ' . $class->name . ' has no field named ' . $field);
|
||||
}
|
||||
array_push($this->_deferredExpressionsStack, $exprStack);
|
||||
|
||||
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.
|
||||
*
|
||||
* PathExpression ::= IdentificationVariable "." {identifier "."}* identifier
|
||||
*
|
||||
* @param integer $expectedType
|
||||
* @param integer $expectedTypes
|
||||
* @return \Doctrine\ORM\Query\AST\PathExpression
|
||||
*/
|
||||
public function PathExpression($expectedType)
|
||||
public function PathExpression($expectedTypes)
|
||||
{
|
||||
$token = $this->_lexer->lookahead;
|
||||
$identVariable = $this->IdentificationVariable();
|
||||
@ -830,23 +988,17 @@ class Parser
|
||||
} while ($this->_lexer->isNextToken(Lexer::T_DOT));
|
||||
|
||||
// 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
|
||||
if ( ! empty($this->_deferredPathExpressionStacks)) {
|
||||
$exprStack = array_pop($this->_deferredPathExpressionStacks);
|
||||
$exprStack[] = array(
|
||||
'pathExpression' => $pathExpr,
|
||||
$exprStack = array(
|
||||
'method' => 'PathExpression',
|
||||
'expression' => $pathExpr,
|
||||
'nestingLevel' => $this->_nestingLevel,
|
||||
'token' => $token,
|
||||
);
|
||||
array_push($this->_deferredPathExpressionStacks, $exprStack);
|
||||
|
||||
return $pathExpr;
|
||||
}
|
||||
|
||||
// Apply PathExpression validation normally (not in defer mode)
|
||||
$this->_validatePathExpression($pathExpr, $this->_nestingLevel, $token);
|
||||
array_push($this->_deferredExpressionsStack, $exprStack);
|
||||
|
||||
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
|
||||
*/
|
||||
@ -984,18 +1136,12 @@ class Parser
|
||||
$this->match(Lexer::T_UPDATE);
|
||||
$token = $this->_lexer->lookahead;
|
||||
$abstractSchemaName = $this->AbstractSchemaName();
|
||||
$aliasIdentificationVariable = null;
|
||||
|
||||
if ($this->_lexer->isNextToken(Lexer::T_AS)) {
|
||||
$this->match(Lexer::T_AS);
|
||||
}
|
||||
|
||||
if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) {
|
||||
$token = $this->_lexer->lookahead;
|
||||
$aliasIdentificationVariable = $this->AliasIdentificationVariable();
|
||||
} else {
|
||||
$aliasIdentificationVariable = $abstractSchemaName;
|
||||
}
|
||||
|
||||
$class = $this->_em->getClassMetadata($abstractSchemaName);
|
||||
|
||||
@ -1011,6 +1157,7 @@ class Parser
|
||||
$this->_queryComponents[$aliasIdentificationVariable] = $queryComponent;
|
||||
|
||||
$this->match(Lexer::T_SET);
|
||||
|
||||
$updateItems = array();
|
||||
$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
|
||||
*/
|
||||
@ -1040,18 +1187,12 @@ class Parser
|
||||
|
||||
$token = $this->_lexer->lookahead;
|
||||
$deleteClause = new AST\DeleteClause($this->AbstractSchemaName());
|
||||
$aliasIdentificationVariable = null;
|
||||
|
||||
if ($this->_lexer->isNextToken(Lexer::T_AS)) {
|
||||
$this->match(Lexer::T_AS);
|
||||
}
|
||||
|
||||
if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) {
|
||||
$token = $this->_lexer->lookahead;
|
||||
$aliasIdentificationVariable = $this->AliasIdentificationVariable();
|
||||
} else {
|
||||
$aliasIdentificationVariable = $deleteClause->abstractSchemaName;
|
||||
}
|
||||
|
||||
$deleteClause->aliasIdentificationVariable = $aliasIdentificationVariable;
|
||||
$class = $this->_em->getClassMetadata($deleteClause->abstractSchemaName);
|
||||
@ -1183,12 +1324,8 @@ class Parser
|
||||
// Increase query nesting level
|
||||
$this->_nestingLevel++;
|
||||
|
||||
$this->_beginDeferredPathExpressionStack();
|
||||
|
||||
$subselect = new AST\Subselect($this->SimpleSelectClause(), $this->SubselectFromClause());
|
||||
|
||||
$this->_processDeferredPathExpressionStack();
|
||||
|
||||
$subselect->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE)
|
||||
? $this->WhereClause() : null;
|
||||
|
||||
@ -1216,17 +1353,14 @@ class Parser
|
||||
public function UpdateItem()
|
||||
{
|
||||
$token = $this->_lexer->lookahead;
|
||||
|
||||
$identVariable = $this->IdentificationVariable();
|
||||
|
||||
// Validate if IdentificationVariable is defined
|
||||
$queryComponent = $this->_validateIdentificationVariable($identVariable, null, $token);
|
||||
|
||||
$this->match(Lexer::T_DOT);
|
||||
$this->match(Lexer::T_IDENTIFIER);
|
||||
$field = $this->_lexer->token['value'];
|
||||
|
||||
// Check if field exists
|
||||
$class = $queryComponent['metadata'];
|
||||
$class = $this->_queryComponents[$identVariable]['metadata'];
|
||||
|
||||
if ( ! isset($class->associationMappings[$field]) && ! isset($class->fieldMappings[$field])) {
|
||||
$this->semanticalError(
|
||||
@ -1285,17 +1419,6 @@ class Parser
|
||||
if ($glimpse['value'] != '.') {
|
||||
$token = $this->_lexer->lookahead;
|
||||
$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 {
|
||||
$expr = $this->StateFieldPathExpression();
|
||||
}
|
||||
@ -1564,7 +1687,7 @@ class Parser
|
||||
|
||||
// Include ResultVariable in query components.
|
||||
$this->_queryComponents[$fieldAliasIdentificationVariable] = array(
|
||||
'resultvariable' => $expression,
|
||||
'resultVariable' => $expression,
|
||||
'nestingLevel' => $this->_nestingLevel,
|
||||
'token' => $token,
|
||||
);
|
||||
|
@ -21,6 +21,8 @@
|
||||
|
||||
namespace Doctrine\ORM\Query;
|
||||
|
||||
use Doctrine\ORM\Query\AST\PathExpression;
|
||||
|
||||
/**
|
||||
* 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.");
|
||||
}
|
||||
|
||||
public static function invalidPathExpression($pathExpr)
|
||||
{
|
||||
return new self(
|
||||
"Invalid PathExpression '" . $pathExpr->identificationVariable .
|
||||
"." . implode('.', $pathExpr->parts) . "'."
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Doctrine\ORM\Mapping\AssociationMapping $assoc
|
||||
*/
|
||||
|
@ -22,6 +22,7 @@
|
||||
namespace Doctrine\ORM\Query;
|
||||
|
||||
use Doctrine\ORM\Query,
|
||||
Doctrine\ORM\Query\QueryException,
|
||||
Doctrine\Common\DoctrineException;
|
||||
|
||||
/**
|
||||
@ -426,11 +427,34 @@ class SqlWalker implements TreeWalker
|
||||
$sql .= $class->getQuotedColumnName($fieldName, $this->_platform);
|
||||
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:
|
||||
throw DoctrineException::notImplemented();
|
||||
|
||||
default:
|
||||
throw DoctrineException::invalidPathExpression($pathExpr->type);
|
||||
throw QueryException::invalidPathExpression($pathExpr);
|
||||
}
|
||||
|
||||
return $sql;
|
||||
|
@ -52,10 +52,6 @@ class DeleteSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
|
||||
parent::assertEquals($sqlToBeConfirmed, $query->getSql());
|
||||
$query->free();
|
||||
} catch (\Exception $e) {
|
||||
if ($debug) {
|
||||
echo $e->getTraceAsString() . PHP_EOL;
|
||||
}
|
||||
|
||||
$this->fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
<?php
|
||||
namespace Doctrine\Tests\ORM\Query;
|
||||
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\Query,
|
||||
Doctrine\ORM\Query\QueryException;
|
||||
|
||||
require_once __DIR__ . '/../../TestInit.php';
|
||||
|
||||
@ -18,7 +19,7 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase
|
||||
{
|
||||
try {
|
||||
$parserResult = $this->parseDql($dql);
|
||||
} catch (\Exception $e) {
|
||||
} catch (QueryException $e) {
|
||||
if ($debug) {
|
||||
echo $e->getTraceAsString() . PHP_EOL;
|
||||
}
|
||||
@ -31,8 +32,9 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase
|
||||
{
|
||||
try {
|
||||
$parserResult = $this->parseDql($dql);
|
||||
|
||||
$this->fail('No syntax errors were detected, when syntax errors were expected');
|
||||
} catch (\Exception $e) {
|
||||
} catch (QueryException $e) {
|
||||
if ($debug) {
|
||||
echo $e->getMessage() . 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 '|'");
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
// This should be allowed because avatar is a single-value association.
|
||||
@ -288,7 +300,7 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase
|
||||
|
||||
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()
|
||||
|
@ -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()
|
||||
{
|
||||
$this->assertSqlGeneration(
|
||||
/*$this->assertSqlGeneration(
|
||||
"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(
|
||||
"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 = ?)"
|
||||
);*/
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user