1
0
mirror of synced 2025-01-22 08:11:40 +03:00

Updated PoC for multiple components DQL support.

This commit is contained in:
Guilherme Blanco 2012-06-10 22:14:25 +02:00 committed by Benjamin Eberlei
parent e7dfa08756
commit 41a650b699
11 changed files with 330 additions and 351 deletions

View File

@ -25,3 +25,8 @@ Also, related functions were affected:
* setParameters($parameters) the argument $parameters can be either an key=>value array or an ArrayCollection instance * setParameters($parameters) the argument $parameters can be either an key=>value array or an ArrayCollection instance
* getParameters() now returns ArrayCollection instead of array * getParameters() now returns ArrayCollection instead of array
* getParameter($key) now returns Parameter instance instead of parameter value * getParameter($key) now returns Parameter instance instead of parameter value
# Query TreeWalker method renamed
Internal changes were made to DQL and SQL generation. If you have implemented your own TreeWalker,
you probably need to update it. The method walkJoinVariableDeclaration is now named walkJoin.

View File

@ -36,13 +36,13 @@ class IdentificationVariableDeclaration extends Node
{ {
public $rangeVariableDeclaration = null; public $rangeVariableDeclaration = null;
public $indexBy = null; public $indexBy = null;
public $joinVariableDeclarations = array(); public $joins = array();
public function __construct($rangeVariableDecl, $indexBy, array $joinVariableDecls) public function __construct($rangeVariableDecl, $indexBy, array $joins)
{ {
$this->rangeVariableDeclaration = $rangeVariableDecl; $this->rangeVariableDeclaration = $rangeVariableDecl;
$this->indexBy = $indexBy; $this->indexBy = $indexBy;
$this->joinVariableDeclarations = $joinVariableDecls; $this->joins = $joins;
} }
public function dispatch($sqlWalker) public function dispatch($sqlWalker)

View File

@ -25,10 +25,8 @@ namespace Doctrine\ORM\Query\AST;
* Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinAssociationPathExpression * Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinAssociationPathExpression
* ["AS"] AliasIdentificationVariable [("ON" | "WITH") ConditionalExpression] * ["AS"] AliasIdentificationVariable [("ON" | "WITH") ConditionalExpression]
* *
*
* @link www.doctrine-project.org * @link www.doctrine-project.org
* @since 2.0 * @since 2.0
* @version $Revision: 3938 $
* @author Guilherme Blanco <guilhermeblanco@hotmail.com> * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com> * @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org> * @author Roman Borschel <roman@code-factory.org>
@ -40,13 +38,13 @@ class Join extends Node
const JOIN_TYPE_INNER = 3; const JOIN_TYPE_INNER = 3;
public $joinType = self::JOIN_TYPE_INNER; public $joinType = self::JOIN_TYPE_INNER;
public $joinPathExpression = null; public $joinAssociationDeclaration = null;
public $conditionalExpression = null; public $conditionalExpression = null;
public function __construct($joinType, $joinPathExpr) public function __construct($joinType, $joinAssociationDeclaration)
{ {
$this->joinType = $joinType; $this->joinType = $joinType;
$this->joinAssociationPathExpression = $joinPathExpr; $this->joinAssociationDeclaration = $joinAssociationDeclaration;
} }
public function dispatch($sqlWalker) public function dispatch($sqlWalker)

View File

@ -15,36 +15,39 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* *
* This software consists of voluntary contributions made by many individuals * This software consists of voluntary contributions made by many individuals
<<<<<<< HEAD:lib/Doctrine/ORM/Query/AST/JoinVariableDeclaration.php
* and is licensed under the MIT license. For more information, see * and is licensed under the MIT license. For more information, see
* <http://www.phpdoctrine.org>. * <http://www.phpdoctrine.org>.
=======
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
>>>>>>> Updated PoC for multiple components DQL support.:lib/Doctrine/ORM/Query/AST/JoinAssociationDeclaration.php
*/ */
namespace Doctrine\ORM\Query\AST; namespace Doctrine\ORM\Query\AST;
/** /**
* JoinVariableDeclaration ::= Join [IndexBy] * JoinAssociationDeclaration ::= JoinAssociationPathExpression ["AS"] AliasIdentificationVariable
*
* *
* @link www.doctrine-project.org * @link www.doctrine-project.org
* @since 2.0 * @since 2.3
* @version $Revision: 3938 $
* @author Guilherme Blanco <guilhermeblanco@hotmail.com> * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/ */
class JoinVariableDeclaration extends Node class JoinAssociationDeclaration extends Node
{ {
public $join = null; public $joinAssociationPathExpression;
public $indexBy = null; public $aliasIdentificationVariable;
public $indexBy;
public function __construct($join, $indexBy) public function __construct($joinAssociationPathExpression, $aliasIdentificationVariable, $indexBy)
{ {
$this->join = $join; $this->joinAssociationPathExpression = $joinAssociationPathExpression;
$this->aliasIdentificationVariable = $aliasIdentificationVariable;
$this->indexBy = $indexBy; $this->indexBy = $indexBy;
} }
public function dispatch($sqlWalker) public function dispatch($sqlWalker)
{ {
return $sqlWalker->walkJoinVariableDeclaration($this); return $sqlWalker->walkJoinAssociationDeclaration($this);
} }
} }

View File

@ -24,10 +24,8 @@ namespace Doctrine\ORM\Query\AST;
/** /**
* JoinAssociationPathExpression ::= IdentificationVariable "." (SingleValuedAssociationField | CollectionValuedAssociationField) * JoinAssociationPathExpression ::= IdentificationVariable "." (SingleValuedAssociationField | CollectionValuedAssociationField)
* *
*
* @link www.doctrine-project.org * @link www.doctrine-project.org
* @since 2.0 * @since 2.0
* @version $Revision: 3938 $
* @author Guilherme Blanco <guilhermeblanco@hotmail.com> * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com> * @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org> * @author Roman Borschel <roman@code-factory.org>
@ -36,17 +34,15 @@ class JoinAssociationPathExpression extends Node
{ {
public $identificationVariable; public $identificationVariable;
public $associationField; public $associationField;
public $aliasIdentificationVariable = null;
public function __construct($identificationVariable, $associationField, $aliasIdentVar) public function __construct($identificationVariable, $associationField)
{ {
$this->identificationVariable = $identificationVariable; $this->identificationVariable = $identificationVariable;
$this->associationField = $associationField; $this->associationField = $associationField;
$this->aliasIdentificationVariable = $aliasIdentVar;
} }
public function dispatch($sqlWalker) public function dispatch($sqlWalker)
{ {
return $sqlWalker->walkJoinPathExpression($this); return $sqlWalker->walkPathExpression($this);
} }
} }

View File

@ -909,58 +909,7 @@ class Parser
$this->semanticalError('Class ' . $class->name . ' has no association named ' . $field); $this->semanticalError('Class ' . $class->name . ' has no association named ' . $field);
} }
if ($this->_lexer->isNextToken(Lexer::T_AS)) { return new AST\JoinAssociationPathExpression($identVariable, $field);
$this->match(Lexer::T_AS);
}
$token = $this->_lexer->lookahead;
$aliasIdentificationVariable = $this->AliasIdentificationVariable();
// Building queryComponent
$joinQueryComponent = array(
'metadata' => $this->_em->getClassMetadata($class->associationMappings[$field]['targetEntity']),
'parent' => $identVariable,
'relation' => $class->getAssociationMapping($field),
'map' => null,
'nestingLevel' => $this->_nestingLevel,
'token' => $this->_lexer->lookahead
);
$this->_queryComponents[$aliasIdentificationVariable] = $joinQueryComponent;
return new AST\JoinAssociationPathExpression($identVariable, $field, $aliasIdentificationVariable);
}
/**
* JoinClassPathExpression ::= Class alias
*
* @return \Doctrine\ORM\Query\AST\JoinClassPathExpression
*/
public function JoinClassPathExpression()
{
$abstractSchemaName = $this->AbstractSchemaName();
if ($this->_lexer->isNextToken(Lexer::T_AS)) {
$this->match(Lexer::T_AS);
}
$token = $this->_lexer->lookahead;
$aliasIdentificationVariable = $this->AliasIdentificationVariable();
$classMetadata = $this->_em->getClassMetadata($abstractSchemaName);
// Building queryComponent
$queryComponent = array(
'metadata' => $classMetadata,
'parent' => null,
'relation' => null,
'map' => null,
'nestingLevel' => $this->_nestingLevel,
'token' => $token
);
$this->_queryComponents[$aliasIdentificationVariable] = $queryComponent;
return new AST\JoinClassPathExpression($abstractSchemaName, $aliasIdentificationVariable);
} }
/** /**
@ -1452,7 +1401,7 @@ class Parser
} }
/** /**
* IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration}* * IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {Join}*
* *
* @return \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration * @return \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration
*/ */
@ -1460,18 +1409,18 @@ class Parser
{ {
$rangeVariableDeclaration = $this->RangeVariableDeclaration(); $rangeVariableDeclaration = $this->RangeVariableDeclaration();
$indexBy = $this->_lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null; $indexBy = $this->_lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null;
$joinVariableDeclarations = array(); $joins = array();
while ( while (
$this->_lexer->isNextToken(Lexer::T_LEFT) || $this->_lexer->isNextToken(Lexer::T_LEFT) ||
$this->_lexer->isNextToken(Lexer::T_INNER) || $this->_lexer->isNextToken(Lexer::T_INNER) ||
$this->_lexer->isNextToken(Lexer::T_JOIN) $this->_lexer->isNextToken(Lexer::T_JOIN)
) { ) {
$joinVariableDeclarations[] = $this->JoinVariableDeclaration(); $joins[] = $this->Join();
} }
return new AST\IdentificationVariableDeclaration( return new AST\IdentificationVariableDeclaration(
$rangeVariableDeclaration, $indexBy, $joinVariableDeclarations $rangeVariableDeclaration, $indexBy, $joins
); );
} }
@ -1501,16 +1450,57 @@ class Parser
} }
/** /**
* JoinVariableDeclaration ::= Join [IndexBy] * Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN"
* (JoinAssociationDeclaration | RangeVariableDeclaration)
* ["WITH" ConditionalExpression]
* *
* @return \Doctrine\ORM\Query\AST\JoinVariableDeclaration * @return \Doctrine\ORM\Query\AST\Join
*/ */
public function JoinVariableDeclaration() public function Join()
{ {
$join = $this->Join(); // Check Join type
$indexBy = $this->_lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null; $joinType = AST\Join::JOIN_TYPE_INNER;
return new AST\JoinVariableDeclaration($join, $indexBy); switch (true) {
case ($this->_lexer->isNextToken(Lexer::T_LEFT)):
$this->match(Lexer::T_LEFT);
$joinType = AST\Join::JOIN_TYPE_LEFT;
// Possible LEFT OUTER join
if ($this->_lexer->isNextToken(Lexer::T_OUTER)) {
$this->match(Lexer::T_OUTER);
$joinType = AST\Join::JOIN_TYPE_LEFTOUTER;
}
break;
case ($this->_lexer->isNextToken(Lexer::T_INNER)):
$this->match(Lexer::T_INNER);
break;
default:
// Do nothing
}
$this->match(Lexer::T_JOIN);
$next = $this->_lexer->glimpse();
$joinDeclaration = ($next['type'] === Lexer::T_DOT)
? $this->JoinAssociationDeclaration()
: $this->RangeVariableDeclaration();
// Create AST node
$join = new AST\Join($joinType, $joinDeclaration);
// Check for ad-hoc Join conditions
if ($this->_lexer->isNextToken(Lexer::T_WITH) || $joinDeclaration instanceof AST\RangeVariableDeclaration) {
$this->match(Lexer::T_WITH);
$join->conditionalExpression = $this->ConditionalExpression();
}
return $join;
} }
/** /**
@ -1545,6 +1535,43 @@ class Parser
return new AST\RangeVariableDeclaration($abstractSchemaName, $aliasIdentificationVariable); return new AST\RangeVariableDeclaration($abstractSchemaName, $aliasIdentificationVariable);
} }
/**
* JoinAssociationDeclaration ::= JoinAssociationPathExpression ["AS"] AliasIdentificationVariable [IndexBy]
*
* @return \Doctrine\ORM\Query\AST\JoinAssociationPathExpression
*/
public function JoinAssociationDeclaration()
{
$joinAssociationPathExpression = $this->JoinAssociationPathExpression();
if ($this->_lexer->isNextToken(Lexer::T_AS)) {
$this->match(Lexer::T_AS);
}
$aliasIdentificationVariable = $this->AliasIdentificationVariable();
$indexBy = $this->_lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null;
$identificationVariable = $joinAssociationPathExpression->identificationVariable;
$field = $joinAssociationPathExpression->associationField;
$class = $this->_queryComponents[$identificationVariable]['metadata'];
$targetClass = $this->_em->getClassMetadata($class->associationMappings[$field]['targetEntity']);
// Building queryComponent
$joinQueryComponent = array(
'metadata' => $targetClass,
'parent' => $joinAssociationPathExpression->identificationVariable,
'relation' => $class->getAssociationMapping($field),
'map' => null,
'nestingLevel' => $this->_nestingLevel,
'token' => $this->_lexer->lookahead
);
$this->_queryComponents[$aliasIdentificationVariable] = $joinQueryComponent;
return new AST\JoinAssociationDeclaration($joinAssociationPathExpression, $aliasIdentificationVariable, $indexBy);
}
/** /**
* PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet * PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet
* PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}" * PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}"
@ -1586,65 +1613,6 @@ class Parser
return $partialObjectExpression; return $partialObjectExpression;
} }
/**
* Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinAssociationPathExpression
* ["AS"] AliasIdentificationVariable ["WITH" ConditionalExpression]
*
* @return \Doctrine\ORM\Query\AST\Join
*/
public function Join()
{
// Check Join type
$joinType = AST\Join::JOIN_TYPE_INNER;
switch (true) {
case ($this->_lexer->isNextToken(Lexer::T_LEFT)):
$this->match(Lexer::T_LEFT);
$joinType = AST\Join::JOIN_TYPE_LEFT;
// Possible LEFT OUTER join
if ($this->_lexer->isNextToken(Lexer::T_OUTER)) {
$this->match(Lexer::T_OUTER);
$joinType = AST\Join::JOIN_TYPE_LEFTOUTER;
}
break;
case ($this->_lexer->isNextToken(Lexer::T_INNER)):
$this->match(Lexer::T_INNER);
break;
default:
// Do nothing
}
$this->match(Lexer::T_JOIN);
$next = $this->_lexer->glimpse();
if ($next['type'] === Lexer::T_DOT) {
$joinPathExpression = $this->JoinAssociationPathExpression();
} else {
$joinPathExpression = $this->JoinClassPathExpression();
if (!$this->_lexer->isNextToken(Lexer::T_WITH)) {
$this->syntaxError('WITH');
}
}
// Create AST node
$join = new AST\Join($joinType, $joinPathExpression);
// Check for ad-hoc Join conditions
if ($this->_lexer->isNextToken(Lexer::T_WITH)) {
$this->match(Lexer::T_WITH);
$join->conditionalExpression = $this->ConditionalExpression();
}
return $join;
}
/** /**
* IndexBy ::= "INDEX" "BY" StateFieldPathExpression * IndexBy ::= "INDEX" "BY" StateFieldPathExpression
* *

View File

@ -705,23 +705,10 @@ class SqlWalker implements TreeWalker
$sqlParts = array(); $sqlParts = array();
foreach ($identificationVarDecls as $identificationVariableDecl) { foreach ($identificationVarDecls as $identificationVariableDecl) {
$sql = ''; $sql = $this->walkRangeVariableDeclaration($identificationVariableDecl->rangeVariableDeclaration);
$rangeDecl = $identificationVariableDecl->rangeVariableDeclaration; foreach ($identificationVariableDecl->joins as $join) {
$dqlAlias = $rangeDecl->aliasIdentificationVariable; $sql .= $this->walkJoin($join);
$this->_rootAliases[] = $dqlAlias;
$class = $this->_em->getClassMetadata($rangeDecl->abstractSchemaName);
$sql .= $class->getQuotedTableName($this->_platform) . ' '
. $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
if ($class->isInheritanceTypeJoined()) {
$sql .= $this->_generateClassTableInheritanceJoins($class, $dqlAlias);
}
foreach ($identificationVariableDecl->joinVariableDeclarations as $joinVarDecl) {
$sql .= $this->walkJoinVariableDeclaration($joinVarDecl);
} }
if ($identificationVariableDecl->indexBy) { if ($identificationVariableDecl->indexBy) {
@ -744,6 +731,174 @@ class SqlWalker implements TreeWalker
return ' FROM ' . implode(', ', $sqlParts); return ' FROM ' . implode(', ', $sqlParts);
} }
/**
* Walks down a RangeVariableDeclaration AST node, thereby generating the appropriate SQL.
*
* @return string
*/
public function walkRangeVariableDeclaration($rangeVariableDeclaration)
{
$class = $this->_em->getClassMetadata($rangeVariableDeclaration->abstractSchemaName);
$dqlAlias = $rangeVariableDeclaration->aliasIdentificationVariable;
$this->_rootAliases[] = $dqlAlias;
$sql = $class->getQuotedTableName($this->_platform) . ' '
. $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
if ($class->isInheritanceTypeJoined()) {
$sql .= $this->_generateClassTableInheritanceJoins($class, $dqlAlias);
}
return $sql;
}
/**
* Walks down a JoinAssociationDeclaration AST node, thereby generating the appropriate SQL.
*
* @return string
*/
public function walkJoinAssociationDeclaration($joinAssociationDeclaration, $joinType = AST\Join::JOIN_TYPE_INNER)
{
$sql = '';
$associationPathExpression = $joinAssociationDeclaration->joinAssociationPathExpression;
$joinedDqlAlias = $joinAssociationDeclaration->aliasIdentificationVariable;
$indexBy = $joinAssociationDeclaration->indexBy;
$relation = $this->_queryComponents[$joinedDqlAlias]['relation'];
$targetClass = $this->_em->getClassMetadata($relation['targetEntity']);
$sourceClass = $this->_em->getClassMetadata($relation['sourceEntity']);
$targetTableName = $targetClass->getQuotedTableName($this->_platform);
$targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName(), $joinedDqlAlias);
$sourceTableAlias = $this->getSQLTableAlias($sourceClass->getTableName(), $associationPathExpression->identificationVariable);
// Ensure we got the owning side, since it has all mapping info
$assoc = ( ! $relation['isOwningSide']) ? $targetClass->associationMappings[$relation['mappedBy']] : $relation;
if ($this->_query->getHint(Query::HINT_INTERNAL_ITERATION) == true && (!$this->_query->getHint(self::HINT_DISTINCT) || isset($this->_selectedClasses[$joinedDqlAlias]))) {
if ($relation['type'] == ClassMetadata::ONE_TO_MANY || $relation['type'] == ClassMetadata::MANY_TO_MANY) {
throw QueryException::iterateWithFetchJoinNotAllowed($assoc);
}
}
// This condition is not checking ClassMetadata::MANY_TO_ONE, because by definition it cannot
// be the owning side and previously we ensured that $assoc is always the owning side of the associations.
// The owning side is necessary at this point because only it contains the JoinColumn information.
switch (true) {
case ($assoc['type'] & ClassMetadata::TO_ONE):
$conditions = array();
foreach ($assoc['sourceToTargetKeyColumns'] as $sourceColumn => $targetColumn) {
if ($relation['isOwningSide']) {
$quotedTargetColumn = ($targetClass->containsForeignIdentifier && !isset($targetClass->fieldNames[$targetColumn]))
? $targetColumn // Join columns cannot be quoted.
: $targetClass->getQuotedColumnName($targetClass->fieldNames[$targetColumn], $this->_platform);
$conditions[] = $sourceTableAlias . '.' . $sourceColumn . ' = ' . $targetTableAlias . '.' . $quotedTargetColumn;
continue;
}
$quotedTargetColumn = ($sourceClass->containsForeignIdentifier && !isset($sourceClass->fieldNames[$targetColumn]))
? $targetColumn // Join columns cannot be quoted.
: $sourceClass->getQuotedColumnName($sourceClass->fieldNames[$targetColumn], $this->_platform);
$conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $targetTableAlias . '.' . $sourceColumn;
}
// Apply remaining inheritance restrictions
$discrSql = $this->_generateDiscriminatorColumnConditionSQL(array($joinedDqlAlias));
if ($discrSql) {
$conditions[] = $discrSql;
}
// Apply the filters
$filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias);
if ($filterExpr) {
$conditions[] = $filterExpr;
}
$sql .= $targetTableName . ' ' . $targetTableAlias . ' ON ' . implode(' AND ', $conditions);
break;
case ($assoc['type'] == ClassMetadata::MANY_TO_MANY):
// Join relation table
$joinTable = $assoc['joinTable'];
$joinTableAlias = $this->getSQLTableAlias($joinTable['name'], $joinedDqlAlias);
$joinTableName = $sourceClass->getQuotedJoinTableName($assoc, $this->_platform);
$conditions = array();
$relationColumns = ($relation['isOwningSide'])
? $assoc['relationToSourceKeyColumns']
: $assoc['relationToTargetKeyColumns'];
foreach ($relationColumns as $relationColumn => $sourceColumn) {
$quotedTargetColumn = ($sourceClass->containsForeignIdentifier && !isset($sourceClass->fieldNames[$sourceColumn]))
? $sourceColumn // Join columns cannot be quoted.
: $sourceClass->getQuotedColumnName($sourceClass->fieldNames[$sourceColumn], $this->_platform);
$conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $relationColumn;
}
$sql .= $joinTableName . ' ' . $joinTableAlias . ' ON ' . implode(' AND ', $conditions);
// Join target table
$sql .= ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) ? ' LEFT JOIN ' : ' INNER JOIN ';
$conditions = array();
$relationColumns = ($relation['isOwningSide'])
? $assoc['relationToTargetKeyColumns']
: $assoc['relationToSourceKeyColumns'];
foreach ($relationColumns as $relationColumn => $targetColumn) {
$quotedTargetColumn = ($targetClass->containsForeignIdentifier && !isset($targetClass->fieldNames[$targetColumn]))
? $targetColumn // Join columns cannot be quoted.
: $targetClass->getQuotedColumnName($targetClass->fieldNames[$targetColumn], $this->_platform);
$conditions[] = $targetTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $relationColumn;
}
// Apply remaining inheritance restrictions
$discrSql = $this->_generateDiscriminatorColumnConditionSQL(array($joinedDqlAlias));
if ($discrSql) {
$conditions[] = $discrSql;
}
// Apply the filters
$filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias);
if ($filterExpr) {
$conditions[] = $filterExpr;
}
$sql .= $targetTableName . ' ' . $targetTableAlias . ' ON ' . implode(' AND ', $conditions);
break;
}
// FIXME: these should either be nested or all forced to be left joins (DDC-XXX)
if ($targetClass->isInheritanceTypeJoined()) {
$sql .= $this->_generateClassTableInheritanceJoins($targetClass, $joinedDqlAlias);
}
// Apply the indexes
if ($indexBy) {
// For Many-To-One or One-To-One associations this obviously makes no sense, but is ignored silently.
$this->_rsm->addIndexBy(
$indexBy->simpleStateFieldPathExpression->identificationVariable,
$indexBy->simpleStateFieldPathExpression->field
);
} else if (isset($relation['indexBy'])) {
$this->_rsm->addIndexBy($joinedDqlAlias, $relation['indexBy']);
}
return $sql;
}
/** /**
* Walks down a FunctionNode AST node, thereby generating the appropriate SQL. * Walks down a FunctionNode AST node, thereby generating the appropriate SQL.
* *
@ -799,139 +954,27 @@ class SqlWalker implements TreeWalker
} }
/** /**
* Walks down a JoinVariableDeclaration AST node and creates the corresponding SQL. * Walks down a Join AST node and creates the corresponding SQL.
* *
* @param JoinVariableDeclaration $joinVarDecl
* @return string The SQL. * @return string The SQL.
*/ */
public function walkJoinVariableDeclaration($joinVarDecl) public function walkJoin($join)
{ {
$join = $joinVarDecl->join;
$joinType = $join->joinType; $joinType = $join->joinType;
$joinDeclaration = $join->joinAssociationDeclaration;
$sql = ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) $sql = ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER)
? ' LEFT JOIN ' ? ' LEFT JOIN '
: ' INNER JOIN '; : ' INNER JOIN ';
$joinPathExpr = $join->joinAssociationPathExpression; switch (true) {
$joinedDqlAlias = $joinPathExpr->aliasIdentificationVariable; case ($joinDeclaration instanceof \Doctrine\ORM\Query\AST\RangeVariableDeclaration):
$sql .= $this->walkRangeVariableDeclaration($joinDeclaration)
. ' ON (' . $this->walkConditionalExpression($join->conditionalExpression) . ')';
break;
if ($joinPathExpr instanceof \Doctrine\ORM\Query\AST\JoinClassPathExpression) { case ($joinDeclaration instanceof \Doctrine\ORM\Query\AST\JoinAssociationDeclaration):
$targetClass = $this->_queryComponents[$joinedDqlAlias]['metadata']; $sql .= $this->walkJoinAssociationDeclaration($joinDeclaration, $joinType);
$targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName(), $joinedDqlAlias);
$sql .= $targetClass->getQuotedTableName($this->_platform) . ' '
. $targetTableAlias;
$sql .= ' ON (' . $this->walkConditionalExpression($join->conditionalExpression) . ')';
} else {
$relation = $this->_queryComponents[$joinedDqlAlias]['relation'];
$targetClass = $this->_em->getClassMetadata($relation['targetEntity']);
$sourceClass = $this->_em->getClassMetadata($relation['sourceEntity']);
$targetTableName = $targetClass->getQuotedTableName($this->_platform);
$targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName(), $joinedDqlAlias);
$sourceTableAlias = $this->getSQLTableAlias($sourceClass->getTableName(), $joinPathExpr->identificationVariable);
// Ensure we got the owning side, since it has all mapping info
$assoc = ( ! $relation['isOwningSide']) ? $targetClass->associationMappings[$relation['mappedBy']] : $relation;
if ($this->_query->getHint(Query::HINT_INTERNAL_ITERATION) == true && (!$this->_query->getHint(self::HINT_DISTINCT) || isset($this->_selectedClasses[$joinedDqlAlias]))) {
if ($relation['type'] == ClassMetadata::ONE_TO_MANY || $relation['type'] == ClassMetadata::MANY_TO_MANY) {
throw QueryException::iterateWithFetchJoinNotAllowed($assoc);
}
}
// This condition is not checking ClassMetadata::MANY_TO_ONE, because by definition it cannot
// be the owning side and previously we ensured that $assoc is always the owning side of the associations.
// The owning side is necessary at this point because only it contains the JoinColumn information.
if ($assoc['type'] & ClassMetadata::TO_ONE) {
$sql .= $targetTableName . ' ' . $targetTableAlias . ' ON ';
$first = true;
foreach ($assoc['sourceToTargetKeyColumns'] as $sourceColumn => $targetColumn) {
if ( ! $first) $sql .= ' AND '; else $first = false;
if ($relation['isOwningSide']) {
if ($targetClass->containsForeignIdentifier && !isset($targetClass->fieldNames[$targetColumn])) {
$quotedTargetColumn = $targetColumn; // Join columns cannot be quoted.
} else {
$quotedTargetColumn = $targetClass->getQuotedColumnName($targetClass->fieldNames[$targetColumn], $this->_platform);
}
$sql .= $sourceTableAlias . '.' . $sourceColumn . ' = ' . $targetTableAlias . '.' . $quotedTargetColumn;
} else {
if ($sourceClass->containsForeignIdentifier && !isset($sourceClass->fieldNames[$targetColumn])) {
$quotedTargetColumn = $targetColumn; // Join columns cannot be quoted.
} else {
$quotedTargetColumn = $sourceClass->getQuotedColumnName($sourceClass->fieldNames[$targetColumn], $this->_platform);
}
$sql .= $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $targetTableAlias . '.' . $sourceColumn;
}
}
} else if ($assoc['type'] == ClassMetadata::MANY_TO_MANY) {
// Join relation table
$joinTable = $assoc['joinTable'];
$joinTableAlias = $this->getSQLTableAlias($joinTable['name'], $joinedDqlAlias);
$sql .= $sourceClass->getQuotedJoinTableName($assoc, $this->_platform) . ' ' . $joinTableAlias . ' ON ';
$first = true;
if ($relation['isOwningSide']) {
foreach ($assoc['relationToSourceKeyColumns'] as $relationColumn => $sourceColumn) {
if ( ! $first) $sql .= ' AND '; else $first = false;
if ($sourceClass->containsForeignIdentifier && !isset($sourceClass->fieldNames[$sourceColumn])) {
$quotedTargetColumn = $sourceColumn; // Join columns cannot be quoted.
} else {
$quotedTargetColumn = $sourceClass->getQuotedColumnName($sourceClass->fieldNames[$sourceColumn], $this->_platform);
}
$sql .= $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $relationColumn;
}
} else {
foreach ($assoc['relationToTargetKeyColumns'] as $relationColumn => $targetColumn) {
if ( ! $first) $sql .= ' AND '; else $first = false;
if ($sourceClass->containsForeignIdentifier && !isset($sourceClass->fieldNames[$targetColumn])) {
$quotedTargetColumn = $targetColumn; // Join columns cannot be quoted.
} else {
$quotedTargetColumn = $sourceClass->getQuotedColumnName($sourceClass->fieldNames[$targetColumn], $this->_platform);
}
$sql .= $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $relationColumn;
}
}
// Join target table
$sql .= ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) ? ' LEFT JOIN ' : ' INNER JOIN ';
$sql .= $targetTableName . ' ' . $targetTableAlias . ' ON ';
$first = true;
if ($relation['isOwningSide']) {
foreach ($assoc['relationToTargetKeyColumns'] as $relationColumn => $targetColumn) {
if ( ! $first) $sql .= ' AND '; else $first = false;
if ($targetClass->containsForeignIdentifier && !isset($targetClass->fieldNames[$targetColumn])) {
$quotedTargetColumn = $targetColumn; // Join columns cannot be quoted.
} else {
$quotedTargetColumn = $targetClass->getQuotedColumnName($targetClass->fieldNames[$targetColumn], $this->_platform);
}
$sql .= $targetTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $relationColumn;
}
} else {
foreach ($assoc['relationToSourceKeyColumns'] as $relationColumn => $sourceColumn) {
if ( ! $first) $sql .= ' AND '; else $first = false;
if ($targetClass->containsForeignIdentifier && !isset($targetClass->fieldNames[$sourceColumn])) {
$quotedTargetColumn = $sourceColumn; // Join columns cannot be quoted.
} else {
$quotedTargetColumn = $targetClass->getQuotedColumnName($targetClass->fieldNames[$sourceColumn], $this->_platform);
}
$sql .= $targetTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $relationColumn;
}
}
}
// Handle WITH clause // Handle WITH clause
if (($condExpr = $join->conditionalExpression) !== null) { if (($condExpr = $join->conditionalExpression) !== null) {
@ -939,33 +982,7 @@ class SqlWalker implements TreeWalker
// if only one ConditionalTerm is defined // if only one ConditionalTerm is defined
$sql .= ' AND (' . $this->walkConditionalExpression($condExpr) . ')'; $sql .= ' AND (' . $this->walkConditionalExpression($condExpr) . ')';
} }
} break;
// Apply the filters
if ($filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias)) {
$sql .= ' AND ' . $filterExpr;
}
if ($joinVarDecl->indexBy) {
// For Many-To-One or One-To-One associations this obviously makes no sense, but is ignored silently.
$this->_rsm->addIndexBy(
$joinVarDecl->indexBy->simpleStateFieldPathExpression->identificationVariable,
$joinVarDecl->indexBy->simpleStateFieldPathExpression->field
);
} else if (isset($relation['indexBy'])) {
$this->_rsm->addIndexBy($joinedDqlAlias, $relation['indexBy']);
}
$discrSql = $this->_generateDiscriminatorColumnConditionSQL(array($joinedDqlAlias));
if ($discrSql) {
$sql .= ' AND ' . $discrSql;
}
// FIXME: these should either be nested or all forced to be left joins (DDC-XXX)
if ($targetClass->isInheritanceTypeJoined()) {
$sql .= $this->_generateClassTableInheritanceJoins($targetClass, $joinedDqlAlias);
} }
return $sql; return $sql;
@ -1308,23 +1325,10 @@ class SqlWalker implements TreeWalker
$sqlParts = array (); $sqlParts = array ();
foreach ($identificationVarDecls as $subselectIdVarDecl) { foreach ($identificationVarDecls as $subselectIdVarDecl) {
$sql = ''; $sql = $this->walkRangeVariableDeclaration($subselectIdVarDecl->rangeVariableDeclaration);
$rangeDecl = $subselectIdVarDecl->rangeVariableDeclaration; foreach ($subselectIdVarDecl->joins as $join) {
$dqlAlias = $rangeDecl->aliasIdentificationVariable; $sql .= $this->walkJoin($join);
$class = $this->_em->getClassMetadata($rangeDecl->abstractSchemaName);
$sql .= $class->getQuotedTableName($this->_platform) . ' '
. $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
$this->_rootAliases[] = $dqlAlias;
if ($class->isInheritanceTypeJoined()) {
$sql .= $this->_generateClassTableInheritanceJoins($class, $dqlAlias);
}
foreach ($subselectIdVarDecl->joinVariableDeclarations as $joinVarDecl) {
$sql .= $this->walkJoinVariableDeclaration($joinVarDecl);
} }
$sqlParts[] = $this->_platform->appendLockHint($sql, $this->_query->getHint(Query::HINT_LOCK_MODE)); $sqlParts[] = $this->_platform->appendLockHint($sql, $this->_query->getHint(Query::HINT_LOCK_MODE));

View File

@ -91,12 +91,12 @@ interface TreeWalker
function walkHavingClause($havingClause); function walkHavingClause($havingClause);
/** /**
* Walks down a JoinVariableDeclaration AST node and creates the corresponding SQL. * Walks down a Join AST node and creates the corresponding SQL.
* *
* @param JoinVariableDeclaration $joinVarDecl * @param Join $joinVarDecl
* @return string The SQL. * @return string The SQL.
*/ */
function walkJoinVariableDeclaration($joinVarDecl); function walkJoin($join);
/** /**
* Walks down a SelectExpression AST node and generates the corresponding SQL. * Walks down a SelectExpression AST node and generates the corresponding SQL.

View File

@ -125,12 +125,12 @@ abstract class TreeWalkerAdapter implements TreeWalker
public function walkHavingClause($havingClause) {} public function walkHavingClause($havingClause) {}
/** /**
* Walks down a JoinVariableDeclaration AST node and creates the corresponding SQL. * Walks down a Join AST node and creates the corresponding SQL.
* *
* @param JoinVariableDeclaration $joinVarDecl * @param Join $join
* @return string The SQL. * @return string The SQL.
*/ */
public function walkJoinVariableDeclaration($joinVarDecl) {} public function walkJoin($join) {}
/** /**
* Walks down a SelectExpression AST node and generates the corresponding SQL. * Walks down a SelectExpression AST node and generates the corresponding SQL.

View File

@ -148,15 +148,15 @@ class TreeWalkerChain implements TreeWalker
} }
/** /**
* Walks down a JoinVariableDeclaration AST node and creates the corresponding SQL. * Walks down a Join AST node and creates the corresponding SQL.
* *
* @param JoinVariableDeclaration $joinVarDecl * @param Join $join
* @return string The SQL. * @return string The SQL.
*/ */
public function walkJoinVariableDeclaration($joinVarDecl) public function walkJoin($join)
{ {
foreach ($this->_walkers as $walker) { foreach ($this->_walkers as $walker) {
$walker->walkJoinVariableDeclaration($joinVarDecl); $walker->walkJoin($join);
} }
} }

View File

@ -206,6 +206,11 @@ class LanguageRecognitionTest extends \Doctrine\Tests\OrmTestCase
$this->assertValidDQL('SELECT u.name, a.topic, p.phonenumber FROM Doctrine\Tests\Models\CMS\CmsUser u INNER JOIN u.articles a LEFT JOIN u.phonenumbers p'); $this->assertValidDQL('SELECT u.name, a.topic, p.phonenumber FROM Doctrine\Tests\Models\CMS\CmsUser u INNER JOIN u.articles a LEFT JOIN u.phonenumbers p');
} }
public function testJoinClassPath()
{
$this->assertValidDQL('SELECT u.name FROM Doctrine\Tests\Models\CMS\CmsUser u JOIN Doctrine\Tests\Models\CMS\CmsArticle a WITH a.user = u.id');
}
public function testOrderBySingleColumn() public function testOrderBySingleColumn()
{ {
$this->assertValidDQL('SELECT u.name FROM Doctrine\Tests\Models\CMS\CmsUser u ORDER BY u.name'); $this->assertValidDQL('SELECT u.name FROM Doctrine\Tests\Models\CMS\CmsUser u ORDER BY u.name');