Updated PoC for multiple components DQL support.
This commit is contained in:
parent
e7dfa08756
commit
41a650b699
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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));
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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');
|
||||
|
Loading…
x
Reference in New Issue
Block a user