Commit 3928ba9d authored by guilhermeblanco's avatar guilhermeblanco

[2.0] Added support to complex PathExpression in DQL queries

parent 20c84166
......@@ -93,7 +93,14 @@ class Parser
* @var EnityManager
*/
private $_em;
/**
* The Abtract Syntax Tree of processed DQL query.
*
* @var SelectStatement | UpdateStatement | DeleteStatement
*/
private $_ast;
/**
* The Query to parse.
*
......@@ -140,6 +147,7 @@ class Parser
$this->_em = $query->getEntityManager();
$this->_lexer = new Lexer($query->getDql());
$this->_parserResult = new ParserResult();
$this->_ast = null;
}
/**
......@@ -193,6 +201,16 @@ class Parser
return $this->_em;
}
/**
* Gets the Abstract Syntax Tree generated by the parser.
*
* @return SelectStatement | UpdateStatement | DeleteStatement
*/
public function getAST()
{
return $this->_ast;
}
/**
* Registers a custom function that returns strings.
*
......@@ -274,10 +292,8 @@ class Parser
// Parse & build AST
$AST = $this->QueryLanguage();
// Check for end of string
if ($this->_lexer->lookahead !== null) {
$this->syntaxError('end of string');
}
// Activate semantical checks after this point. Process all deferred checks in pipeline
$this->_processDeferredExpressionsStack($AST);
if ($customWalkers = $this->_query->getHint(Query::HINT_CUSTOM_TREE_WALKERS)) {
$this->_customTreeWalkers = $customWalkers;
......@@ -286,9 +302,11 @@ class Parser
// Run any custom tree walkers over the AST
if ($this->_customTreeWalkers) {
$treeWalkerChain = new TreeWalkerChain($this->_query, $this->_parserResult, $this->_queryComponents);
foreach ($this->_customTreeWalkers as $walker) {
$treeWalkerChain->addTreeWalker($walker);
}
if ($AST instanceof AST\SelectStatement) {
$treeWalkerChain->walkSelectStatement($AST);
} else if ($AST instanceof AST\UpdateStatement) {
......@@ -492,11 +510,11 @@ class Parser
* @param array $token
* @param integer $nestingLevel
*/
private function _subscribeExpression($expression, $method, $token, $nextingLevel = null)
private function _subscribeExpression($expression, $method, $token, $nestingLevel = null)
{
$nestingLevel = ($nestingLevel !== null) ?: $this->_nestingLevel;
$exprStack[] = array(
$exprStack = array(
'method' => $method,
'expression' => $expression,
'nestingLevel' => $nestingLevel,
......@@ -508,13 +526,17 @@ class Parser
/**
* Processes the topmost stack of deferred path expressions.
*
* @param mixed $AST
*/
private function _processDeferredExpressionsStack()
private function _processDeferredExpressionsStack($AST)
{
foreach ($this->_deferredExpressionsStack as $item) {
if (!isset($item['method'])) var_dump($item);
$method = '_validate' . $item['method'];
$this->$method($item['expression'], $item['token'], $item['nestingLevel']);
$this->$method($item, $AST);
}
}
......@@ -523,30 +545,35 @@ class Parser
* 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
* @param array $deferredItem
* @param mixed $AST
*
* @return array Query Component
*/
private function _validateIdentificationVariable($identVariable, $token, $nestingLevel)
private function _validateIdentificationVariable($deferredItem, $AST)
{
$identVariable = $deferredItem['expression'];
// Check if IdentificationVariable exists in queryComponents
if ( ! isset($this->_queryComponents[$identVariable])) {
$this->semanticalError("'$identVariable' is not defined.", $token);
$this->semanticalError(
"'$identVariable' is not defined.", $deferredItem['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);
$this->semanticalError(
"'$identVariable' does not point to a Class.", $deferredItem['token']
);
}
// Validate if identification variable nesting level is lower or equal than the current one
if ($qComp['nestingLevel'] > $nestingLevel) {
if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
$this->semanticalError(
"'$identVariable' is used outside the scope of its declaration.", $token
"'$identVariable' is used outside the scope of its declaration.", $deferredItem['token']
);
}
......@@ -554,75 +581,38 @@ class Parser
}
/**
* 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.
* Validates that the given <tt>ResultVariable</tt> is a semantically correct.
* It must exist in query components list.
*
* @param string $resultVariable
* @param array $token
* @param integer $nestingLevel
* @param array $deferredItem
* @param mixed $AST
*
* @return array Query Component
*/
private function _validateResultVariable($resultVariable, $token, $nestingLevel)
private function _validateResultVariable($deferredItem, $AST)
{
$resultVariable = $deferredItem['expression'];
// Check if ResultVariable exists in queryComponents
if ( ! isset($this->_queryComponents[$resultVariable])) {
$this->semanticalError("'$resultVariable' is not defined.", $token);
$this->semanticalError(
"'$resultVariable' is not defined.", $deferredItem['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);
$this->semanticalError(
"'$identVariable' does not point to a ResultVariable.", $deferredItem['token']
);
}
// Validate if identification variable nesting level is lower or equal than the current one
if ($qComp['nestingLevel'] > $nestingLevel) {
if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
$this->semanticalError(
"'$resultVariable' is used outside the scope of its declaration.", $token
"'$resultVariable' is used outside the scope of its declaration.", $deferredItem['token']
);
}
......@@ -632,14 +622,14 @@ class Parser
/**
* Validates that the given <tt>JoinAssociationPathExpression</tt> is a semantically correct.
*
* @param JoinAssociationPathExpression $pathExpression
* @param array $token
* @param integer $nestingLevel
* @param array $deferredItem
* @param mixed $AST
*
* @return array Query Component
*/
private function _validateJoinAssociationPathExpression(AST\JoinAssociationPathExpression $pathExpression, $token, $nestingLevel)
private function _validateJoinAssociationPathExpression($deferredItem, $AST)
{
$pathExpression = $deferredItem['expression'];
$qComp = $this->_queryComponents[$pathExpression->identificationVariable];;
// Validating association field (*-to-one or *-to-many)
......@@ -662,70 +652,106 @@ class Parser
* SingleValuedAssociationPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* SingleValuedAssociationField
* CollectionValuedPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* CollectionValuedAssociationField
*
* @param PathExpression $pathExpression
* @param array $token
* @param integer $nestingLevel
* @param array $deferredItem
* @param mixed $AST
*
* @return integer
*/
private function _validatePathExpression(AST\PathExpression $pathExpression, $token, $nestingLevel)
private function _validatePathExpression($deferredItem, $AST)
{
$qComp = $this->_queryComponents[$pathExpression->identificationVariable];
$pathExpression = $deferredItem['expression'];
$parts = $pathExpression->parts;
$numParts = count($parts);
$qComp = $this->_queryComponents[$pathExpression->identificationVariable];
$aliasIdentificationVariable = $pathExpression->identificationVariable;
$parentField = $pathExpression->identificationVariable;
$class = $qComp['metadata'];
$stateField = $collectionField = null;
foreach ($pathExpression->parts as $field) {
$fieldType = null;
$curIndex = 0;
foreach ($parts as $field) {
// Check if it is not in a state field
if ($stateField !== null) {
if ($fieldType & AST\PathExpression::TYPE_STATE_FIELD) {
$this->semanticalError(
'Cannot navigate through state field named ' . $stateField, $token
'Cannot navigate through state field named ' . $field . ' on ' . $parentField,
$deferredItem['token']
);
}
// Check if it is not a collection field
if ($collectionField !== null) {
if ($fieldType & AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION) {
$this->semanticalError(
'Cannot navigate through collection-valued field named ' . $collectionField,
$token
'Cannot navigate through collection field named ' . $field . ' on ' . $parentField,
$deferredItem['token']
);
}
// Check if field exists
// Check if field or association exists
if ( ! isset($class->associationMappings[$field]) && ! isset($class->fieldMappings[$field])) {
$this->semanticalError(
'Class ' . $class->name . ' has no field or association named ' . $field, $token
'Class ' . $class->name . ' has no field or association named ' . $field,
$deferredItem['token']
);
}
$parentField = $field;
if (isset($class->fieldMappings[$field])) {
$stateField = $field;
} else if ($class->associationMappings[$field]->isOneToOne()) {
$class = $this->_em->getClassMetadata($class->associationMappings[$field]->targetEntityName);
$fieldType = AST\PathExpression::TYPE_STATE_FIELD;
} else {
$collectionField = $field;
$assoc = $class->associationMappings[$field];
$class = $this->_em->getClassMetadata($assoc->targetEntityName);
if (
($curIndex != $numParts - 1) &&
! isset($this->_queryComponents[$aliasIdentificationVariable . '.' . $field])
) {
// Building queryComponent
$joinQueryComponent = array(
'metadata' => $class,
'parent' => $aliasIdentificationVariable,
'relation' => $assoc,
'map' => null,
'nestingLevel' => $this->_nestingLevel,
'token' => $deferredItem['token'],
);
// Create AST node
$joinVariableDeclaration = new AST\JoinVariableDeclaration(
new AST\Join(
AST\Join::JOIN_TYPE_INNER,
new AST\JoinAssociationPathExpression($aliasIdentificationVariable, $field),
$aliasIdentificationVariable . '.' . $field
),
null
);
$AST->fromClause->identificationVariableDeclarations[0]->joinVariableDeclarations[] = $joinVariableDeclaration;
$this->_queryComponents[$aliasIdentificationVariable . '.' . $field] = $joinQueryComponent;
}
$aliasIdentificationVariable .= '.' . $field;
if ($assoc->isOneToOne()) {
$fieldType = AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION;
} else {
$fieldType = AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION;
}
}
$curIndex++;
}
// Recognize correct expression type
$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;
}
// Validate if PathExpression is one of the expected types
$expectedType = $pathExpression->expectedType;
if ( ! ($expectedType & $expressionType)) {
if ( ! ($expectedType & $fieldType)) {
// We need to recognize which was expected type(s)
$expectedStringTypes = array();
// Validate state field type (field/column)
// Validate state field type
if ($expectedType & AST\PathExpression::TYPE_STATE_FIELD) {
$expectedStringTypes[] = 'StateFieldPathExpression';
}
......@@ -749,15 +775,14 @@ class Parser
$semanticalError .= implode(' or ', $expectedStringTypes) . ' expected.';
}
$this->semanticalError($semanticalError, $token);
$this->semanticalError($semanticalError, $deferredItem['token']);
}
// We need to force the type in PathExpression
$pathExpression->type = $expressionType;
$pathExpression->type = $fieldType;
return $expressionType;
return $fieldType;
}
/**
* QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement
......@@ -769,7 +794,7 @@ class Parser
public function QueryLanguage()
{
$this->_lexer->moveNext();
switch ($this->_lexer->lookahead['type']) {
case Lexer::T_SELECT:
return $this->SelectStatement();
......@@ -784,6 +809,11 @@ class Parser
$this->syntaxError('SELECT, UPDATE or DELETE');
break;
}
// Check for end of string
if ($this->_lexer->lookahead !== null) {
$this->syntaxError('end of string');
}
}
......@@ -807,10 +837,7 @@ 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;
}
......@@ -824,9 +851,6 @@ class Parser
$updateStatement = new AST\UpdateStatement($this->UpdateClause());
$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;
}
......@@ -841,9 +865,6 @@ class Parser
$deleteStatement = new AST\DeleteStatement($this->DeleteClause());
$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;
}
......@@ -883,11 +904,13 @@ class Parser
$this->match(Lexer::T_IDENTIFIER);
$aliasIdentVariable = $this->_lexer->token['value'];
$exists = isset($this->_queryComponents[$aliasIdentVariable]);
// Apply AliasIdentificationVariable validation
$this->_validateAliasIdentificationVariable(
$aliasIdentVariable, $this->_lexer->token, $this->_nestingLevel
);
if ($exists) {
$this->semanticalError(
"'$aliasIdentVariable' is already defined.", $this->_lexer->token
);
}
return $aliasIdentVariable;
}
......@@ -902,15 +925,36 @@ class Parser
$this->match(Lexer::T_IDENTIFIER);
$schemaName = $this->_lexer->token['value'];
$exists = class_exists($schemaName, true);
// Apply AbstractSchemaName validation
$this->_validateAbstractSchemaName(
$schemaName, $this->_lexer->token, $this->_nestingLevel
);
if ( ! $exists) {
$this->semanticalError("Class '$schemaName' is not defined.", $this->_lexer->token);
}
return $schemaName;
}
/**
* AliasResultVariable ::= identifier
*
* @return string
*/
public function AliasResultVariable()
{
$this->match(Lexer::T_IDENTIFIER);
$resultVariable = $this->_lexer->token['value'];
$exists = isset($this->_queryComponents[$resultVariable]);
if ($exists) {
$this->semanticalError(
"'$resultVariable' is already defined.", $this->_lexer->token
);
}
return $resultVariable;
}
/**
* ResultVariable ::= identifier
*
......@@ -923,15 +967,11 @@ class Parser
$resultVariable = $this->_lexer->token['value'];
// Defer ResultVariable validation
$exprStack = array(
'method' => 'ResultVariable',
'expression' => $resultVariable,
'nestingLevel' => $this->_nestingLevel,
'token' => $this->_lexer->token,
$this->_subscribeExpression(
$resultVariable, 'ResultVariable',
$this->_lexer->token, $this->_nestingLevel
);
array_push($this->_deferredExpressionsStack, $exprStack);
return $resultVariable;
}
......@@ -953,14 +993,10 @@ class Parser
$pathExpr = new AST\JoinAssociationPathExpression($identVariable, $field);
// Defer JoinAssociationPathExpression validation
$exprStack = array(
'method' => 'JoinAssociationPathExpression',
'expression' => $pathExpr,
'nestingLevel' => $this->_nestingLevel,
'token' => $token,
$this->_subscribeExpression(
$pathExpr, 'JoinAssociationPathExpression',
$token, $this->_nestingLevel
);
array_push($this->_deferredExpressionsStack, $exprStack);
return $pathExpr;
}
......@@ -969,7 +1005,7 @@ class Parser
* Parses an arbitrary path expression and defers semantical validation
* based on expected types.
*
* PathExpression ::= IdentificationVariable "." {identifier "."}* identifier
* PathExpression ::= IdentificationVariable {"." identifier}* "." identifier
*
* @param integer $expectedTypes
* @return \Doctrine\ORM\Query\AST\PathExpression
......@@ -991,14 +1027,10 @@ class Parser
$pathExpr = new AST\PathExpression($expectedTypes, $identVariable, $parts);
// Defer PathExpression validation if requested to be defered
$exprStack = array(
'method' => 'PathExpression',
'expression' => $pathExpr,
'nestingLevel' => $this->_nestingLevel,
'token' => $token,
$this->_subscribeExpression(
$pathExpr, 'PathExpression',
$token, $this->_nestingLevel
);
array_push($this->_deferredExpressionsStack, $exprStack);
return $pathExpr;
}
......@@ -1598,7 +1630,7 @@ class Parser
);
}
$targetClassName = $parentClass->getAssociationMapping($assocField)->getTargetEntityName();
$targetClassName = $parentClass->getAssociationMapping($assocField)->targetEntityName;
// Building queryComponent
$joinQueryComponent = array(
......@@ -1651,7 +1683,7 @@ class Parser
/**
* SelectExpression ::=
* IdentificationVariable | StateFieldPathExpression |
* (AggregateExpression | "(" Subselect ")" | FunctionDeclaration) [["AS"] ResultVariable]
* (AggregateExpression | "(" Subselect ")" | FunctionDeclaration) [["AS"] AliasResultVariable]
*
* @return \Doctrine\ORM\Query\AST\SelectExpression
*/
......@@ -1683,9 +1715,9 @@ class Parser
if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) {
$token = $this->_lexer->lookahead;
$fieldAliasIdentificationVariable = $this->ResultVariable();
$fieldAliasIdentificationVariable = $this->AliasResultVariable();
// Include ResultVariable in query components.
// Include AliasResultVariable in query components.
$this->_queryComponents[$fieldAliasIdentificationVariable] = array(
'resultVariable' => $expression,
'nestingLevel' => $this->_nestingLevel,
......@@ -1708,7 +1740,7 @@ class Parser
}
/**
* SimpleSelectExpression ::= StateFieldPathExpression | IdentificationVariable | (AggregateExpression [["AS"] ResultVariable])
* SimpleSelectExpression ::= StateFieldPathExpression | IdentificationVariable | (AggregateExpression [["AS"] AliasResultVariable])
*
* @return \Doctrine\ORM\Query\AST\SimpleSelectExpression
*/
......@@ -1735,10 +1767,10 @@ class Parser
if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) {
$token = $this->_lexer->lookahead;
$resultVariable = $this->ResultVariable();
$resultVariable = $this->AliasResultVariable();
$expr->fieldIdentificationVariable = $resultVariable;
// Include ResultVariable in query components.
// Include AliasResultVariable in query components.
$this->_queryComponents[$resultVariable] = array(
'resultvariable' => $expr,
'nestingLevel' => $this->_nestingLevel,
......
......@@ -414,9 +414,8 @@ class SqlWalker implements TreeWalker
switch ($pathExpr->type) {
case AST\PathExpression::TYPE_STATE_FIELD:
$parts = $pathExpr->parts;
$numParts = count($parts);
$dqlAlias = $pathExpr->identificationVariable;
$fieldName = $parts[$numParts - 1];
$fieldName = array_pop($parts);
$dqlAlias = $pathExpr->identificationVariable . (( ! empty($parts)) ? '.' . implode('.', $parts) : '');
$qComp = $this->_queryComponents[$dqlAlias];
$class = $qComp['metadata'];
......@@ -609,20 +608,18 @@ class SqlWalker implements TreeWalker
*/
public function walkOrderByItem($orderByItem)
{
$sql = '';
$expr = $orderByItem->expression;
if ($expr instanceof AST\PathExpression) {
$parts = $expr->parts;
$dqlAlias = $expr->identificationVariable;
$class = $this->_queryComponents[$dqlAlias]['metadata'];
$columnName = $class->getQuotedColumnName($parts[0], $this->_platform);
return $this->getSqlTableAlias($class->getTableName(), $dqlAlias) . '.'
. $columnName . ' ' . strtoupper($orderByItem->type);
$sql = $this->walkPathExpression($expr);
} else {
$columnName = $this->_queryComponents[$expr]['token']['value'];
return $this->_scalarResultAliasMap[$columnName] . ' ' . strtoupper($orderByItem->type);
$sql = $this->_scalarResultAliasMap[$columnName];
}
return $sql . ' ' . strtoupper($orderByItem->type);;
}
/**
......@@ -771,16 +768,15 @@ class SqlWalker implements TreeWalker
if ($expr instanceof AST\PathExpression) {
if ($expr->type == AST\PathExpression::TYPE_STATE_FIELD) {
$parts = $expr->parts;
$numParts = count($parts);
$dqlAlias = $expr->identificationVariable;
$fieldName = $parts[$numParts - 1];
$fieldName = array_pop($parts);
$dqlAlias = $expr->identificationVariable . (( ! empty($parts)) ? '.' . implode('.', $parts) : '');
$qComp = $this->_queryComponents[$dqlAlias];
$class = $qComp['metadata'];
if ( ! isset($this->_selectedClasses[$dqlAlias])) {
$this->_selectedClasses[$dqlAlias] = $class;
}
$sqlTableAlias = $this->getSqlTableAlias($class->getTableName(), $dqlAlias);
$columnName = $class->getQuotedColumnName($fieldName, $this->_platform);
$columnAlias = $this->getSqlColumnAlias($class->columnNames[$fieldName]);
......@@ -1004,17 +1000,8 @@ class SqlWalker implements TreeWalker
*/
public function walkAggregateExpression($aggExpression)
{
$sql = '';
$parts = $aggExpression->pathExpression->parts;
$dqlAlias = $aggExpression->pathExpression->identificationVariable;
$fieldName = $parts[0];
$qComp = $this->_queryComponents[$dqlAlias];
$columnName = $qComp['metadata']->getQuotedColumnName($fieldName, $this->_platform);
return $aggExpression->functionName . '(' . ($aggExpression->isDistinct ? 'DISTINCT ' : '')
. $this->getSqlTableAlias($qComp['metadata']->getTableName(), $dqlAlias) . '.'
. $columnName . ')';
. $this->walkPathExpression($aggExpression->pathExpression) . ')';
}
/**
......@@ -1038,12 +1025,7 @@ class SqlWalker implements TreeWalker
*/
public function walkGroupByItem(AST\PathExpression $pathExpr)
{
$parts = $pathExpr->parts;
$dqlAlias = $pathExpr->identificationVariable;
$qComp = $this->_queryComponents[$dqlAlias];
$columnName = $qComp['metadata']->getQuotedColumnName($parts[0], $this->_platform);
return $this->getSqlTableAlias($qComp['metadata']->getTableName(), $dqlAlias) . '.' . $columnName;
return $this->walkPathExpression($pathExpr);
}
/**
......@@ -1219,8 +1201,10 @@ class SqlWalker implements TreeWalker
$sql .= 'EXISTS (SELECT 1 FROM ';
$entityExpr = $collMemberExpr->entityExpression;
$collPathExpr = $collMemberExpr->collectionValuedPathExpression;
$parts = $collPathExpr->parts;
$dqlAlias = $collPathExpr->identificationVariable;
$fieldName = array_pop($parts);
$dqlAlias = $collPathExpr->identificationVariable . (( ! empty($parts)) ? '.' . implode('.', $parts) : '');
$class = $this->_queryComponents[$dqlAlias]['metadata'];
......@@ -1232,7 +1216,7 @@ class SqlWalker implements TreeWalker
throw DoctrineException::notImplemented();
}
$assoc = $class->associationMappings[$parts[0]];
$assoc = $class->associationMappings[$fieldName];
if ($assoc->isOneToMany()) {
$targetClass = $this->_em->getClassMetadata($assoc->targetEntityName);
......
......@@ -58,6 +58,25 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase
return $parser->parse();
}
public function parseDqlForAST($dql, $hints = array())
{
$query = $this->_em->createQuery($dql);
$query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true);
$query->setDql($dql);
foreach ($hints as $key => $value) {
$query->setHint($key, $value);
}
$parser = new \Doctrine\ORM\Query\Parser($query);
// We do NOT test SQL output here. That only unnecessarily slows down the tests!
$parser->setCustomOutputTreeWalker('Doctrine\Tests\Mocks\MockTreeWalker');
$parser->parse();
return $parser->getAST();
}
public function testEmptyQueryString()
{
......@@ -84,6 +103,11 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase
$this->assertValidDql('SELECT u, p FROM Doctrine\Tests\Models\CMS\CmsUser u JOIN u.phonenumbers p');
}
public function testSelectMultipleComponentsWithAsterisk2()
{
$this->assertValidDql('SELECT a.user.name FROM Doctrine\Tests\Models\CMS\CmsArticle a');
}
public function testSelectDistinctIsSupported()
{
$this->assertValidDql('SELECT DISTINCT u.name FROM Doctrine\Tests\Models\CMS\CmsUser u');
......@@ -93,6 +117,11 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase
{
$this->assertValidDql('SELECT COUNT(u.id) FROM Doctrine\Tests\Models\CMS\CmsUser u');
}
public function testDuplicatedAliasInAggregateFunction()
{
$this->assertInvalidDql('SELECT COUNT(u.id) AS num, SUM(u.id) AS num FROM Doctrine\Tests\Models\CMS\CmsUser u');
}
public function testAggregateFunctionWithDistinctInSelect()
{
......
......@@ -2,7 +2,8 @@
namespace Doctrine\Tests\ORM\Query;
use Doctrine\ORM\Query;
use Doctrine\ORM\Query,
Doctrine\Common\DoctrineException;
require_once __DIR__ . '/../../TestInit.php';
......@@ -22,7 +23,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
$query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true);
parent::assertEquals($sqlToBeConfirmed, $query->getSql());
$query->free();
} catch (Doctrine_Exception $e) {
} catch (DoctrineException $e) {
$this->fail($e->getMessage());
}
}
......@@ -42,6 +43,22 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
'SELECT c0_.id AS id0 FROM cms_users c0_'
);
}
public function testSupportsSelectForOneNestedField()
{
$this->assertSqlGeneration(
'SELECT a.user.id FROM Doctrine\Tests\Models\CMS\CmsArticle a',
'SELECT c0_.id AS id0 FROM cms_articles c1_ INNER JOIN cms_users c0_ ON c1_.user_id = c0_.id'
);
}
public function testSupportsSelectForAllNestedField()
{
$this->assertSqlGeneration(
'SELECT a FROM Doctrine\Tests\Models\CMS\CmsArticle a ORDER BY a.user.name ASC',
'SELECT c0_.id AS id0, c0_.topic AS topic1, c0_.text AS text2 FROM cms_articles c0_ INNER JOIN cms_users c1_ ON c0_.user_id = c1_.id ORDER BY c1_.name ASC'
);
}
public function testSupportsSelectForMultipleColumnsOfASingleComponent()
{
......
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