[PoC] Arbitrary join support
This commit is contained in:
parent
27b4f58b66
commit
e7dfa08756
@ -40,15 +40,13 @@ class Join extends Node
|
|||||||
const JOIN_TYPE_INNER = 3;
|
const JOIN_TYPE_INNER = 3;
|
||||||
|
|
||||||
public $joinType = self::JOIN_TYPE_INNER;
|
public $joinType = self::JOIN_TYPE_INNER;
|
||||||
public $joinAssociationPathExpression = null;
|
public $joinPathExpression = null;
|
||||||
public $aliasIdentificationVariable = null;
|
|
||||||
public $conditionalExpression = null;
|
public $conditionalExpression = null;
|
||||||
|
|
||||||
public function __construct($joinType, $joinAssocPathExpr, $aliasIdentVar)
|
public function __construct($joinType, $joinPathExpr)
|
||||||
{
|
{
|
||||||
$this->joinType = $joinType;
|
$this->joinType = $joinType;
|
||||||
$this->joinAssociationPathExpression = $joinAssocPathExpr;
|
$this->joinAssociationPathExpression = $joinPathExpr;
|
||||||
$this->aliasIdentificationVariable = $aliasIdentVar;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function dispatch($sqlWalker)
|
public function dispatch($sqlWalker)
|
||||||
|
@ -36,11 +36,13 @@ class JoinAssociationPathExpression extends Node
|
|||||||
{
|
{
|
||||||
public $identificationVariable;
|
public $identificationVariable;
|
||||||
public $associationField;
|
public $associationField;
|
||||||
|
public $aliasIdentificationVariable = null;
|
||||||
|
|
||||||
public function __construct($identificationVariable, $associationField)
|
public function __construct($identificationVariable, $associationField, $aliasIdentVar)
|
||||||
{
|
{
|
||||||
$this->identificationVariable = $identificationVariable;
|
$this->identificationVariable = $identificationVariable;
|
||||||
$this->associationField = $associationField;
|
$this->associationField = $associationField;
|
||||||
|
$this->aliasIdentificationVariable = $aliasIdentVar;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function dispatch($sqlWalker)
|
public function dispatch($sqlWalker)
|
||||||
|
47
lib/Doctrine/ORM/Query/AST/JoinClassPathExpression.php
Normal file
47
lib/Doctrine/ORM/Query/AST/JoinClassPathExpression.php
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<?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
|
||||||
|
* and is licensed under the LGPL. For more information, see
|
||||||
|
* <http://www.doctrine-project.org>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Doctrine\ORM\Query\AST;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JoinClassPathExpression ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
|
||||||
|
*
|
||||||
|
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||||
|
* @link www.doctrine-project.org
|
||||||
|
* @since 2.3
|
||||||
|
* @author Alexander <iam.asm89@gmail.com>
|
||||||
|
*/
|
||||||
|
class JoinClassPathExpression extends Node
|
||||||
|
{
|
||||||
|
public $abstractSchemaName;
|
||||||
|
public $aliasIdentificationVariable;
|
||||||
|
|
||||||
|
public function __construct($abstractSchemaName, $aliasIdentificationVar)
|
||||||
|
{
|
||||||
|
$this->abstractSchemaName = $abstractSchemaName;
|
||||||
|
$this->aliasIdentificationVariable = $aliasIdentificationVar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function dispatch($walker)
|
||||||
|
{
|
||||||
|
return $sqlWalker->walkJoinPathExpression($this);
|
||||||
|
}
|
||||||
|
}
|
@ -905,11 +905,62 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new AST\JoinAssociationPathExpression($identVariable, $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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1570,41 +1621,19 @@ class Parser
|
|||||||
|
|
||||||
$this->match(Lexer::T_JOIN);
|
$this->match(Lexer::T_JOIN);
|
||||||
|
|
||||||
$joinPathExpression = $this->JoinAssociationPathExpression();
|
$next = $this->_lexer->glimpse();
|
||||||
|
if ($next['type'] === Lexer::T_DOT) {
|
||||||
|
$joinPathExpression = $this->JoinAssociationPathExpression();
|
||||||
|
} else {
|
||||||
|
$joinPathExpression = $this->JoinClassPathExpression();
|
||||||
|
|
||||||
if ($this->_lexer->isNextToken(Lexer::T_AS)) {
|
if (!$this->_lexer->isNextToken(Lexer::T_WITH)) {
|
||||||
$this->match(Lexer::T_AS);
|
$this->syntaxError('WITH');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$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
|
// Create AST node
|
||||||
$join = new AST\Join($joinType, $joinPathExpression, $aliasIdentificationVariable);
|
$join = new AST\Join($joinType, $joinPathExpression);
|
||||||
|
|
||||||
// Check for ad-hoc Join conditions
|
// Check for ad-hoc Join conditions
|
||||||
if ($this->_lexer->isNextToken(Lexer::T_WITH)) {
|
if ($this->_lexer->isNextToken(Lexer::T_WITH)) {
|
||||||
|
@ -812,31 +812,138 @@ class SqlWalker implements TreeWalker
|
|||||||
? ' LEFT JOIN '
|
? ' LEFT JOIN '
|
||||||
: ' INNER JOIN ';
|
: ' INNER JOIN ';
|
||||||
|
|
||||||
if ($joinVarDecl->indexBy) {
|
$joinPathExpr = $join->joinAssociationPathExpression;
|
||||||
// For Many-To-One or One-To-One associations this obviously makes no sense, but is ignored silently.
|
$joinedDqlAlias = $joinPathExpr->aliasIdentificationVariable;
|
||||||
$this->_rsm->addIndexBy(
|
|
||||||
$joinVarDecl->indexBy->simpleStateFieldPathExpression->identificationVariable,
|
if ($joinPathExpr instanceof \Doctrine\ORM\Query\AST\JoinClassPathExpression) {
|
||||||
$joinVarDecl->indexBy->simpleStateFieldPathExpression->field
|
$targetClass = $this->_queryComponents[$joinedDqlAlias]['metadata'];
|
||||||
);
|
|
||||||
|
$targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName(), $joinedDqlAlias);
|
||||||
|
$sql .= $targetClass->getQuotedTableName($this->_platform) . ' '
|
||||||
|
. $targetTableAlias;
|
||||||
|
|
||||||
|
$sql .= ' ON (' . $this->walkConditionalExpression($join->conditionalExpression) . ')';
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$relation = $this->_queryComponents[$joinedDqlAlias]['relation'];
|
||||||
|
$targetClass = $this->_em->getClassMetadata($relation['targetEntity']);
|
||||||
|
$sourceClass = $this->_em->getClassMetadata($relation['sourceEntity']);
|
||||||
|
$targetTableName = $targetClass->getQuotedTableName($this->_platform);
|
||||||
|
|
||||||
|
$targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName(), $joinedDqlAlias);
|
||||||
|
$sourceTableAlias = $this->getSQLTableAlias($sourceClass->getTableName(), $joinPathExpr->identificationVariable);
|
||||||
|
|
||||||
|
// Ensure we got the owning side, since it has all mapping info
|
||||||
|
$assoc = ( ! $relation['isOwningSide']) ? $targetClass->associationMappings[$relation['mappedBy']] : $relation;
|
||||||
|
if ($this->_query->getHint(Query::HINT_INTERNAL_ITERATION) == true && (!$this->_query->getHint(self::HINT_DISTINCT) || isset($this->_selectedClasses[$joinedDqlAlias]))) {
|
||||||
|
if ($relation['type'] == ClassMetadata::ONE_TO_MANY || $relation['type'] == ClassMetadata::MANY_TO_MANY) {
|
||||||
|
throw QueryException::iterateWithFetchJoinNotAllowed($assoc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This condition is not checking ClassMetadata::MANY_TO_ONE, because by definition it cannot
|
||||||
|
// be the owning side and previously we ensured that $assoc is always the owning side of the associations.
|
||||||
|
// The owning side is necessary at this point because only it contains the JoinColumn information.
|
||||||
|
if ($assoc['type'] & ClassMetadata::TO_ONE) {
|
||||||
|
$sql .= $targetTableName . ' ' . $targetTableAlias . ' ON ';
|
||||||
|
$first = true;
|
||||||
|
|
||||||
|
foreach ($assoc['sourceToTargetKeyColumns'] as $sourceColumn => $targetColumn) {
|
||||||
|
if ( ! $first) $sql .= ' AND '; else $first = false;
|
||||||
|
|
||||||
|
if ($relation['isOwningSide']) {
|
||||||
|
if ($targetClass->containsForeignIdentifier && !isset($targetClass->fieldNames[$targetColumn])) {
|
||||||
|
$quotedTargetColumn = $targetColumn; // Join columns cannot be quoted.
|
||||||
|
} else {
|
||||||
|
$quotedTargetColumn = $targetClass->getQuotedColumnName($targetClass->fieldNames[$targetColumn], $this->_platform);
|
||||||
|
}
|
||||||
|
$sql .= $sourceTableAlias . '.' . $sourceColumn . ' = ' . $targetTableAlias . '.' . $quotedTargetColumn;
|
||||||
|
} else {
|
||||||
|
if ($sourceClass->containsForeignIdentifier && !isset($sourceClass->fieldNames[$targetColumn])) {
|
||||||
|
$quotedTargetColumn = $targetColumn; // Join columns cannot be quoted.
|
||||||
|
} else {
|
||||||
|
$quotedTargetColumn = $sourceClass->getQuotedColumnName($sourceClass->fieldNames[$targetColumn], $this->_platform);
|
||||||
|
}
|
||||||
|
$sql .= $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $targetTableAlias . '.' . $sourceColumn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if ($assoc['type'] == ClassMetadata::MANY_TO_MANY) {
|
||||||
|
// Join relation table
|
||||||
|
$joinTable = $assoc['joinTable'];
|
||||||
|
$joinTableAlias = $this->getSQLTableAlias($joinTable['name'], $joinedDqlAlias);
|
||||||
|
$sql .= $sourceClass->getQuotedJoinTableName($assoc, $this->_platform) . ' ' . $joinTableAlias . ' ON ';
|
||||||
|
|
||||||
|
$first = true;
|
||||||
|
if ($relation['isOwningSide']) {
|
||||||
|
foreach ($assoc['relationToSourceKeyColumns'] as $relationColumn => $sourceColumn) {
|
||||||
|
if ( ! $first) $sql .= ' AND '; else $first = false;
|
||||||
|
|
||||||
|
if ($sourceClass->containsForeignIdentifier && !isset($sourceClass->fieldNames[$sourceColumn])) {
|
||||||
|
$quotedTargetColumn = $sourceColumn; // Join columns cannot be quoted.
|
||||||
|
} else {
|
||||||
|
$quotedTargetColumn = $sourceClass->getQuotedColumnName($sourceClass->fieldNames[$sourceColumn], $this->_platform);
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql .= $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $relationColumn;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
foreach ($assoc['relationToTargetKeyColumns'] as $relationColumn => $targetColumn) {
|
||||||
|
if ( ! $first) $sql .= ' AND '; else $first = false;
|
||||||
|
|
||||||
|
if ($sourceClass->containsForeignIdentifier && !isset($sourceClass->fieldNames[$targetColumn])) {
|
||||||
|
$quotedTargetColumn = $targetColumn; // Join columns cannot be quoted.
|
||||||
|
} else {
|
||||||
|
$quotedTargetColumn = $sourceClass->getQuotedColumnName($sourceClass->fieldNames[$targetColumn], $this->_platform);
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql .= $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $relationColumn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join target table
|
||||||
|
$sql .= ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) ? ' LEFT JOIN ' : ' INNER JOIN ';
|
||||||
|
$sql .= $targetTableName . ' ' . $targetTableAlias . ' ON ';
|
||||||
|
|
||||||
|
$first = true;
|
||||||
|
if ($relation['isOwningSide']) {
|
||||||
|
foreach ($assoc['relationToTargetKeyColumns'] as $relationColumn => $targetColumn) {
|
||||||
|
if ( ! $first) $sql .= ' AND '; else $first = false;
|
||||||
|
|
||||||
|
if ($targetClass->containsForeignIdentifier && !isset($targetClass->fieldNames[$targetColumn])) {
|
||||||
|
$quotedTargetColumn = $targetColumn; // Join columns cannot be quoted.
|
||||||
|
} else {
|
||||||
|
$quotedTargetColumn = $targetClass->getQuotedColumnName($targetClass->fieldNames[$targetColumn], $this->_platform);
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql .= $targetTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $relationColumn;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
foreach ($assoc['relationToSourceKeyColumns'] as $relationColumn => $sourceColumn) {
|
||||||
|
if ( ! $first) $sql .= ' AND '; else $first = false;
|
||||||
|
|
||||||
|
if ($targetClass->containsForeignIdentifier && !isset($targetClass->fieldNames[$sourceColumn])) {
|
||||||
|
$quotedTargetColumn = $sourceColumn; // Join columns cannot be quoted.
|
||||||
|
} else {
|
||||||
|
$quotedTargetColumn = $targetClass->getQuotedColumnName($targetClass->fieldNames[$sourceColumn], $this->_platform);
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql .= $targetTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $relationColumn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle WITH clause
|
||||||
|
if (($condExpr = $join->conditionalExpression) !== null) {
|
||||||
|
// Phase 2 AST optimization: Skip processment of ConditionalExpression
|
||||||
|
// if only one ConditionalTerm is defined
|
||||||
|
$sql .= ' AND (' . $this->walkConditionalExpression($condExpr) . ')';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$joinAssocPathExpr = $join->joinAssociationPathExpression;
|
// Apply the filters
|
||||||
$joinedDqlAlias = $join->aliasIdentificationVariable;
|
if ($filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias)) {
|
||||||
|
$sql .= ' AND ' . $filterExpr;
|
||||||
$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(), $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) {
|
if ($joinVarDecl->indexBy) {
|
||||||
@ -849,109 +956,6 @@ class SqlWalker implements TreeWalker
|
|||||||
$this->_rsm->addIndexBy($joinedDqlAlias, $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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} 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));
|
$discrSql = $this->_generateDiscriminatorColumnConditionSQL(array($joinedDqlAlias));
|
||||||
|
|
||||||
|
@ -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