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

@ -24,4 +24,9 @@ Also, related functions were affected:
* iterate($parameters, $hydrationMode) 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
* 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 $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->indexBy = $indexBy;
$this->joinVariableDeclarations = $joinVariableDecls;
$this->joins = $joins;
}
public function dispatch($sqlWalker)

View File

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

View File

@ -15,36 +15,39 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* 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
* <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;
/**
* JoinVariableDeclaration ::= Join [IndexBy]
* JoinAssociationDeclaration ::= JoinAssociationPathExpression ["AS"] AliasIdentificationVariable
*
*
* @link www.doctrine-project.org
* @since 2.0
* @version $Revision: 3938 $
* @since 2.3
* @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 $indexBy = null;
public $joinAssociationPathExpression;
public $aliasIdentificationVariable;
public $indexBy;
public function __construct($join, $indexBy)
public function __construct($joinAssociationPathExpression, $aliasIdentificationVariable, $indexBy)
{
$this->join = $join;
$this->indexBy = $indexBy;
$this->joinAssociationPathExpression = $joinAssociationPathExpression;
$this->aliasIdentificationVariable = $aliasIdentificationVariable;
$this->indexBy = $indexBy;
}
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)
*
*
* @link www.doctrine-project.org
* @since 2.0
* @version $Revision: 3938 $
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
@ -36,17 +34,15 @@ class JoinAssociationPathExpression extends Node
{
public $identificationVariable;
public $associationField;
public $aliasIdentificationVariable = null;
public function __construct($identificationVariable, $associationField, $aliasIdentVar)
public function __construct($identificationVariable, $associationField)
{
$this->identificationVariable = $identificationVariable;
$this->associationField = $associationField;
$this->aliasIdentificationVariable = $aliasIdentVar;
$this->associationField = $associationField;
}
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);
}
if ($this->_lexer->isNextToken(Lexer::T_AS)) {
$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);
return new AST\JoinAssociationPathExpression($identVariable, $field);
}
/**
@ -1452,7 +1401,7 @@ class Parser
}
/**
* IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration}*
* IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {Join}*
*
* @return \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration
*/
@ -1460,18 +1409,18 @@ class Parser
{
$rangeVariableDeclaration = $this->RangeVariableDeclaration();
$indexBy = $this->_lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null;
$joinVariableDeclarations = array();
$joins = array();
while (
$this->_lexer->isNextToken(Lexer::T_LEFT) ||
$this->_lexer->isNextToken(Lexer::T_INNER) ||
$this->_lexer->isNextToken(Lexer::T_JOIN)
) {
$joinVariableDeclarations[] = $this->JoinVariableDeclaration();
$joins[] = $this->Join();
}
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();
$indexBy = $this->_lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null;
// Check Join type
$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);
}
/**
* 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
* PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}"
@ -1586,65 +1613,6 @@ class Parser
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
*

View File

@ -705,23 +705,10 @@ class SqlWalker implements TreeWalker
$sqlParts = array();
foreach ($identificationVarDecls as $identificationVariableDecl) {
$sql = '';
$sql = $this->walkRangeVariableDeclaration($identificationVariableDecl->rangeVariableDeclaration);
$rangeDecl = $identificationVariableDecl->rangeVariableDeclaration;
$dqlAlias = $rangeDecl->aliasIdentificationVariable;
$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);
foreach ($identificationVariableDecl->joins as $join) {
$sql .= $this->walkJoin($join);
}
if ($identificationVariableDecl->indexBy) {
@ -744,6 +731,174 @@ class SqlWalker implements TreeWalker
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.
*
@ -799,173 +954,35 @@ 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.
*/
public function walkJoinVariableDeclaration($joinVarDecl)
public function walkJoin($join)
{
$join = $joinVarDecl->join;
$joinType = $join->joinType;
$sql = ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER)
$joinType = $join->joinType;
$joinDeclaration = $join->joinAssociationDeclaration;
$sql = ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER)
? ' LEFT JOIN '
: ' INNER JOIN ';
$joinPathExpr = $join->joinAssociationPathExpression;
$joinedDqlAlias = $joinPathExpr->aliasIdentificationVariable;
switch (true) {
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) {
$targetClass = $this->_queryComponents[$joinedDqlAlias]['metadata'];
case ($joinDeclaration instanceof \Doctrine\ORM\Query\AST\JoinAssociationDeclaration):
$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);
// Handle WITH clause
if (($condExpr = $join->conditionalExpression) !== null) {
// Phase 2 AST optimization: Skip processment of ConditionalExpression
// if only one ConditionalTerm is defined
$sql .= ' AND (' . $this->walkConditionalExpression($condExpr) . ')';
}
}
// 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
if (($condExpr = $join->conditionalExpression) !== null) {
// Phase 2 AST optimization: Skip processment of ConditionalExpression
// if only one ConditionalTerm is defined
$sql .= ' AND (' . $this->walkConditionalExpression($condExpr) . ')';
}
}
// 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);
break;
}
return $sql;
@ -1308,23 +1325,10 @@ class SqlWalker implements TreeWalker
$sqlParts = array ();
foreach ($identificationVarDecls as $subselectIdVarDecl) {
$sql = '';
$sql = $this->walkRangeVariableDeclaration($subselectIdVarDecl->rangeVariableDeclaration);
$rangeDecl = $subselectIdVarDecl->rangeVariableDeclaration;
$dqlAlias = $rangeDecl->aliasIdentificationVariable;
$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);
foreach ($subselectIdVarDecl->joins as $join) {
$sql .= $this->walkJoin($join);
}
$sqlParts[] = $this->_platform->appendLockHint($sql, $this->_query->getHint(Query::HINT_LOCK_MODE));

View File

@ -91,12 +91,12 @@ interface TreeWalker
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.
*/
function walkJoinVariableDeclaration($joinVarDecl);
function walkJoin($join);
/**
* 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) {}
/**
* 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.
*/
public function walkJoinVariableDeclaration($joinVarDecl) {}
public function walkJoin($join) {}
/**
* 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.
*/
public function walkJoinVariableDeclaration($joinVarDecl)
public function walkJoin($join)
{
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');
}
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()
{
$this->assertValidDQL('SELECT u.name FROM Doctrine\Tests\Models\CMS\CmsUser u ORDER BY u.name');