commit
a3210e78aa
@ -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
|
* 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
|
* 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.
|
||||||
|
@ -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)
|
||||||
|
@ -25,30 +25,26 @@ 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>
|
||||||
*/
|
*/
|
||||||
class Join extends Node
|
class Join extends Node
|
||||||
{
|
{
|
||||||
const JOIN_TYPE_LEFT = 1;
|
const JOIN_TYPE_LEFT = 1;
|
||||||
const JOIN_TYPE_LEFTOUTER = 2;
|
const JOIN_TYPE_LEFTOUTER = 2;
|
||||||
const JOIN_TYPE_INNER = 3;
|
const JOIN_TYPE_INNER = 3;
|
||||||
|
|
||||||
public $joinType = self::JOIN_TYPE_INNER;
|
public $joinType = self::JOIN_TYPE_INNER;
|
||||||
public $joinAssociationPathExpression = null;
|
public $joinAssociationDeclaration = null;
|
||||||
public $aliasIdentificationVariable = null;
|
|
||||||
public $conditionalExpression = null;
|
public $conditionalExpression = null;
|
||||||
|
|
||||||
public function __construct($joinType, $joinAssocPathExpr, $aliasIdentVar)
|
public function __construct($joinType, $joinAssociationDeclaration)
|
||||||
{
|
{
|
||||||
$this->joinType = $joinType;
|
$this->joinType = $joinType;
|
||||||
$this->joinAssociationPathExpression = $joinAssocPathExpr;
|
$this->joinAssociationDeclaration = $joinAssociationDeclaration;
|
||||||
$this->aliasIdentificationVariable = $aliasIdentVar;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function dispatch($sqlWalker)
|
public function dispatch($sqlWalker)
|
||||||
|
53
lib/Doctrine/ORM/Query/AST/JoinAssociationDeclaration.php
Normal file
53
lib/Doctrine/ORM/Query/AST/JoinAssociationDeclaration.php
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* $Id$
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JoinAssociationDeclaration ::= JoinAssociationPathExpression ["AS"] AliasIdentificationVariable
|
||||||
|
*
|
||||||
|
* @link www.doctrine-project.org
|
||||||
|
* @since 2.3
|
||||||
|
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||||
|
*/
|
||||||
|
class JoinAssociationDeclaration extends Node
|
||||||
|
{
|
||||||
|
public $joinAssociationPathExpression;
|
||||||
|
public $aliasIdentificationVariable;
|
||||||
|
public $indexBy;
|
||||||
|
|
||||||
|
public function __construct($joinAssociationPathExpression, $aliasIdentificationVariable, $indexBy)
|
||||||
|
{
|
||||||
|
$this->joinAssociationPathExpression = $joinAssociationPathExpression;
|
||||||
|
$this->aliasIdentificationVariable = $aliasIdentificationVariable;
|
||||||
|
$this->indexBy = $indexBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function dispatch($sqlWalker)
|
||||||
|
{
|
||||||
|
return $sqlWalker->walkJoinAssociationDeclaration($this);
|
||||||
|
}
|
||||||
|
}
|
@ -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>
|
||||||
@ -40,11 +38,11 @@ class JoinAssociationPathExpression extends Node
|
|||||||
public function __construct($identificationVariable, $associationField)
|
public function __construct($identificationVariable, $associationField)
|
||||||
{
|
{
|
||||||
$this->identificationVariable = $identificationVariable;
|
$this->identificationVariable = $identificationVariable;
|
||||||
$this->associationField = $associationField;
|
$this->associationField = $associationField;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function dispatch($sqlWalker)
|
public function dispatch($sqlWalker)
|
||||||
{
|
{
|
||||||
return $sqlWalker->walkJoinPathExpression($this);
|
return $sqlWalker->walkPathExpression($this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,36 +15,33 @@
|
|||||||
* 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
|
||||||
* and is licensed under the MIT license. For more information, see
|
* and is licensed under the LGPL. For more information, see
|
||||||
* <http://www.phpdoctrine.org>.
|
* <http://www.doctrine-project.org>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Doctrine\ORM\Query\AST;
|
namespace Doctrine\ORM\Query\AST;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JoinVariableDeclaration ::= Join [IndexBy]
|
* JoinClassPathExpression ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
|
||||||
*
|
*
|
||||||
*
|
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||||
* @link www.doctrine-project.org
|
* @link www.doctrine-project.org
|
||||||
* @since 2.0
|
* @since 2.3
|
||||||
* @version $Revision: 3938 $
|
* @author Alexander <iam.asm89@gmail.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 JoinClassPathExpression extends Node
|
||||||
{
|
{
|
||||||
public $join = null;
|
public $abstractSchemaName;
|
||||||
public $indexBy = null;
|
public $aliasIdentificationVariable;
|
||||||
|
|
||||||
public function __construct($join, $indexBy)
|
public function __construct($abstractSchemaName, $aliasIdentificationVar)
|
||||||
{
|
{
|
||||||
$this->join = $join;
|
$this->abstractSchemaName = $abstractSchemaName;
|
||||||
$this->indexBy = $indexBy;
|
$this->aliasIdentificationVariable = $aliasIdentificationVar;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function dispatch($sqlWalker)
|
public function dispatch($walker)
|
||||||
{
|
{
|
||||||
return $sqlWalker->walkJoinVariableDeclaration($this);
|
return $sqlWalker->walkJoinPathExpression($this);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -905,7 +905,7 @@ class Parser
|
|||||||
$qComp = $this->_queryComponents[$identVariable];
|
$qComp = $this->_queryComponents[$identVariable];
|
||||||
$class = $qComp['metadata'];
|
$class = $qComp['metadata'];
|
||||||
|
|
||||||
if ( ! isset($class->associationMappings[$field])) {
|
if ( ! $class->hasAssociation($field)) {
|
||||||
$this->semanticalError('Class ' . $class->name . ' has no association named ' . $field);
|
$this->semanticalError('Class ' . $class->name . ' has no association named ' . $field);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1401,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
|
||||||
*/
|
*/
|
||||||
@ -1409,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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1450,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1494,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}* "}"
|
||||||
@ -1535,87 +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);
|
|
||||||
|
|
||||||
$joinPathExpression = $this->JoinAssociationPathExpression();
|
|
||||||
|
|
||||||
if ($this->_lexer->isNextToken(Lexer::T_AS)) {
|
|
||||||
$this->match(Lexer::T_AS);
|
|
||||||
}
|
|
||||||
|
|
||||||
$token = $this->_lexer->lookahead;
|
|
||||||
$aliasIdentificationVariable = $this->AliasIdentificationVariable();
|
|
||||||
|
|
||||||
// Verify that the association exists.
|
|
||||||
$parentClass = $this->_queryComponents[$joinPathExpression->identificationVariable]['metadata'];
|
|
||||||
$assocField = $joinPathExpression->associationField;
|
|
||||||
|
|
||||||
if ( ! $parentClass->hasAssociation($assocField)) {
|
|
||||||
$this->semanticalError(
|
|
||||||
"Class " . $parentClass->name . " has no association named '$assocField'."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$targetClassName = $parentClass->associationMappings[$assocField]['targetEntity'];
|
|
||||||
|
|
||||||
// Building queryComponent
|
|
||||||
$joinQueryComponent = array(
|
|
||||||
'metadata' => $this->_em->getClassMetadata($targetClassName),
|
|
||||||
'parent' => $joinPathExpression->identificationVariable,
|
|
||||||
'relation' => $parentClass->getAssociationMapping($assocField),
|
|
||||||
'map' => null,
|
|
||||||
'nestingLevel' => $this->_nestingLevel,
|
|
||||||
'token' => $token
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->_queryComponents[$aliasIdentificationVariable] = $joinQueryComponent;
|
|
||||||
|
|
||||||
// Create AST node
|
|
||||||
$join = new AST\Join($joinType, $joinPathExpression, $aliasIdentificationVariable);
|
|
||||||
|
|
||||||
// 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
|
||||||
*
|
*
|
||||||
|
@ -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,169 +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.
|
* @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 ';
|
||||||
|
|
||||||
if ($joinVarDecl->indexBy) {
|
switch (true) {
|
||||||
// For Many-To-One or One-To-One associations this obviously makes no sense, but is ignored silently.
|
case ($joinDeclaration instanceof \Doctrine\ORM\Query\AST\RangeVariableDeclaration):
|
||||||
$this->_rsm->addIndexBy(
|
$sql .= $this->walkRangeVariableDeclaration($joinDeclaration)
|
||||||
$joinVarDecl->indexBy->simpleStateFieldPathExpression->identificationVariable,
|
. ' ON (' . $this->walkConditionalExpression($join->conditionalExpression) . ')';
|
||||||
$joinVarDecl->indexBy->simpleStateFieldPathExpression->field
|
break;
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$joinAssocPathExpr = $join->joinAssociationPathExpression;
|
case ($joinDeclaration instanceof \Doctrine\ORM\Query\AST\JoinAssociationDeclaration):
|
||||||
$joinedDqlAlias = $join->aliasIdentificationVariable;
|
$sql .= $this->walkJoinAssociationDeclaration($joinDeclaration, $joinType);
|
||||||
|
|
||||||
$relation = $this->_queryComponents[$joinedDqlAlias]['relation'];
|
// Handle WITH clause
|
||||||
$targetClass = $this->_em->getClassMetadata($relation['targetEntity']);
|
if (($condExpr = $join->conditionalExpression) !== null) {
|
||||||
$sourceClass = $this->_em->getClassMetadata($relation['sourceEntity']);
|
// Phase 2 AST optimization: Skip processment of ConditionalExpression
|
||||||
$targetTableName = $targetClass->getQuotedTableName($this->_platform);
|
// if only one ConditionalTerm is defined
|
||||||
|
$sql .= ' AND (' . $this->walkConditionalExpression($condExpr) . ')';
|
||||||
$targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName(), $joinedDqlAlias);
|
|
||||||
$sourceTableAlias = $this->getSQLTableAlias($sourceClass->getTableName(), $joinAssocPathExpr->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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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']);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
|
|
||||||
} 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply the filters
|
|
||||||
if ($filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias)) {
|
|
||||||
$sql .= ' AND ' . $filterExpr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) . ')';
|
|
||||||
}
|
|
||||||
|
|
||||||
$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;
|
||||||
@ -1304,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));
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,14 +5,13 @@ namespace Doctrine\Tests\ORM\Functional;
|
|||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
|
||||||
use Doctrine\DBAL\Connection;
|
use Doctrine\DBAL\Connection;
|
||||||
|
use Doctrine\Tests\Models\CMS\CmsUser,
|
||||||
|
Doctrine\Tests\Models\CMS\CmsArticle,
|
||||||
|
Doctrine\Tests\Models\CMS\CmsPhonenumber;
|
||||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||||
use Doctrine\ORM\Query;
|
use Doctrine\ORM\Query;
|
||||||
use Doctrine\ORM\Query\Parameter;
|
use Doctrine\ORM\Query\Parameter;
|
||||||
|
|
||||||
use Doctrine\Tests\Models\CMS\CmsUser;
|
|
||||||
use Doctrine\Tests\Models\CMS\CmsArticle;
|
|
||||||
|
|
||||||
require_once __DIR__ . '/../../TestInit.php';
|
require_once __DIR__ . '/../../TestInit.php';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -713,4 +712,72 @@ class QueryTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
|||||||
$this->assertInstanceOf('\Doctrine\ORM\NonUniqueResultException', $exc);
|
$this->assertInstanceOf('\Doctrine\ORM\NonUniqueResultException', $exc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testMultipleJoinComponentsUsingInnerJoin()
|
||||||
|
{
|
||||||
|
$userA = new CmsUser;
|
||||||
|
$userA->name = 'Benjamin';
|
||||||
|
$userA->username = 'beberlei';
|
||||||
|
$userA->status = 'developer';
|
||||||
|
|
||||||
|
$phonenumberA = new CmsPhonenumber;
|
||||||
|
$phonenumberA->phonenumber = '111111';
|
||||||
|
$userA->addPhonenumber($phonenumberA);
|
||||||
|
|
||||||
|
$userB = new CmsUser;
|
||||||
|
$userB->name = 'Alexander';
|
||||||
|
$userB->username = 'asm89';
|
||||||
|
$userB->status = 'developer';
|
||||||
|
|
||||||
|
$this->_em->persist($userA);
|
||||||
|
$this->_em->persist($userB);
|
||||||
|
$this->_em->flush();
|
||||||
|
$this->_em->clear();
|
||||||
|
|
||||||
|
$query = $this->_em->createQuery("
|
||||||
|
SELECT u, p
|
||||||
|
FROM Doctrine\Tests\Models\CMS\CmsUser u
|
||||||
|
INNER JOIN Doctrine\Tests\Models\CMS\CmsPhonenumber p WITH u = p.user
|
||||||
|
");
|
||||||
|
$users = $query->execute();
|
||||||
|
|
||||||
|
$this->assertEquals(2, count($users));
|
||||||
|
$this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $users[0]);
|
||||||
|
$this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsPhonenumber', $users[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMultipleJoinComponentsUsingLeftJoin()
|
||||||
|
{
|
||||||
|
$userA = new CmsUser;
|
||||||
|
$userA->name = 'Benjamin';
|
||||||
|
$userA->username = 'beberlei';
|
||||||
|
$userA->status = 'developer';
|
||||||
|
|
||||||
|
$phonenumberA = new CmsPhonenumber;
|
||||||
|
$phonenumberA->phonenumber = '111111';
|
||||||
|
$userA->addPhonenumber($phonenumberA);
|
||||||
|
|
||||||
|
$userB = new CmsUser;
|
||||||
|
$userB->name = 'Alexander';
|
||||||
|
$userB->username = 'asm89';
|
||||||
|
$userB->status = 'developer';
|
||||||
|
|
||||||
|
$this->_em->persist($userA);
|
||||||
|
$this->_em->persist($userB);
|
||||||
|
$this->_em->flush();
|
||||||
|
$this->_em->clear();
|
||||||
|
|
||||||
|
$query = $this->_em->createQuery("
|
||||||
|
SELECT u, p
|
||||||
|
FROM Doctrine\Tests\Models\CMS\CmsUser u
|
||||||
|
LEFT JOIN Doctrine\Tests\Models\CMS\CmsPhonenumber p WITH u = p.user
|
||||||
|
");
|
||||||
|
$users = $query->execute();
|
||||||
|
|
||||||
|
$this->assertEquals(4, count($users));
|
||||||
|
$this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $users[0]);
|
||||||
|
$this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsPhonenumber', $users[1]);
|
||||||
|
$this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $users[2]);
|
||||||
|
$this->assertNull($users[3]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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');
|
||||||
|
@ -135,6 +135,14 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testSupportsJoinOnMultipleComponents()
|
||||||
|
{
|
||||||
|
$this->assertSqlGeneration(
|
||||||
|
'SELECT u, p FROM Doctrine\Tests\Models\CMS\CmsUser u JOIN Doctrine\Tests\Models\CMS\CmsPhonenumber p WITH u = p.user',
|
||||||
|
'SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3, c1_.phonenumber AS phonenumber4 FROM cms_users c0_ INNER JOIN cms_phonenumbers c1_ ON (c0_.id = c1_.user_id)'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function testSupportsSelectWithCollectionAssociationJoin()
|
public function testSupportsSelectWithCollectionAssociationJoin()
|
||||||
{
|
{
|
||||||
$this->assertSqlGeneration(
|
$this->assertSqlGeneration(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user