1
0
mirror of synced 2025-02-20 22:23:14 +03:00

Made DQL and EBNF in sync.

This commit is contained in:
Guilherme Blanco 2014-04-20 20:27:57 +00:00
parent a3f95d919b
commit 6a8ee87268
7 changed files with 304 additions and 124 deletions

View File

@ -1440,6 +1440,12 @@ Identifiers
/* identifier that must be a class name (the "User" of "FROM User u") */
AbstractSchemaName ::= identifier
/* Alias ResultVariable declaration (the "total" of "COUNT(*) AS total") */
AliasResultVariable = identifier
/* ResultVariable identifier usage of mapped field aliases (the "total" of "COUNT(*) AS total") */
ResultVariable = identifier
/* identifier that must be a field (the "name" of "u.name") */
/* This is responsible to know if the field exists in Object, no matter if it's a relation or a simple field */
FieldIdentificationVariable ::= identifier
@ -1450,19 +1456,13 @@ Identifiers
/* identifier that must be a single-valued association field (to-one) (the "Group" of "u.Group") */
SingleValuedAssociationField ::= FieldIdentificationVariable
/* identifier that must be an embedded class state field (for the future) */
/* identifier that must be an embedded class state field */
EmbeddedClassStateField ::= FieldIdentificationVariable
/* identifier that must be a simple state field (name, email, ...) (the "name" of "u.name") */
/* The difference between this and FieldIdentificationVariable is only semantical, because it points to a single field (not mapping to a relation) */
SimpleStateField ::= FieldIdentificationVariable
/* Alias ResultVariable declaration (the "total" of "COUNT(*) AS total") */
AliasResultVariable = identifier
/* ResultVariable identifier usage of mapped field aliases (the "total" of "COUNT(*) AS total") */
ResultVariable = identifier
Path Expressions
~~~~~~~~~~~~~~~~
@ -1521,11 +1521,11 @@ From, Join and Index by
.. code-block:: php
IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration}*
SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable)
JoinVariableDeclaration ::= Join [IndexBy]
IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {Join}*
SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration
RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinAssociationPathExpression ["AS"] AliasIdentificationVariable ["WITH" ConditionalExpression]
JoinAssociationDeclaration ::= JoinAssociationPathExpression ["AS"] AliasIdentificationVariable [IndexBy]
Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" (JoinAssociationDeclaration | RangeVariableDeclaration) ["WITH" ConditionalExpression]
IndexBy ::= "INDEX" "BY" StateFieldPathExpression
Select Expressions
@ -1533,10 +1533,12 @@ Select Expressions
.. code-block:: php
SelectExpression ::= (IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression) [["AS"] ["HIDDEN"] AliasResultVariable]
SelectExpression ::= (IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression | NewObjectExpression) [["AS"] ["HIDDEN"] AliasResultVariable]
SimpleSelectExpression ::= (StateFieldPathExpression | IdentificationVariable | FunctionDeclaration | AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] AliasResultVariable]
PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet
PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}"
NewObjectExpression ::= "NEW" IdentificationVariable "(" NewObjectArg {"," NewObjectArg}* ")"
NewObjectArg ::= ScalarExpression | "(" Subselect ")"
Conditional Expressions
~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -0,0 +1,61 @@
<?php
/*
* 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 MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Query\AST;
/**
* JoinVariableDeclaration ::= Join [IndexBy]
*
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link www.doctrine-project.org
* @since 2.5
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
*/
class JoinVariableDeclaration extends Node
{
/**
* @var Join
*/
public $join;
/**
* @var IndexBy|null
*/
public $indexBy;
/**
* Constructor.
*
* @param Join $join
* @param IndexBy|null $indexBy
*/
public function __construct($join, $indexBy)
{
$this->join = $join;
$this->indexBy = $indexBy;
}
/**
* {@inheritdoc}
*/
public function dispatch($walker)
{
return $walker->walkJoinVariableDeclaration($this);
}
}

View File

@ -0,0 +1,52 @@
<?php
/*
* 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 MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Query\AST;
/**
* SubselectIdentificationVariableDeclaration ::= AssociationPathExpression ["AS"] AliasIdentificationVariable
*
* @link www.doctrine-project.org
* @since 2.0
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
*/
class SubselectIdentificationVariableDeclaration
{
/**
* @var PathExpression
*/
public $associationPathExpression;
/**
* @var string
*/
public $aliasIdentificationVariable;
/**
* Constructor.
*
* @param PathExpression $associationPathExpression
* @param string $aliasIdentificationVariable
*/
public function __construct($associationPathExpression, $aliasIdentificationVariable)
{
$this->associationPathExpression = $associationPathExpression;
$this->aliasIdentificationVariable = $aliasIdentificationVariable;
}
}

View File

@ -1049,7 +1049,7 @@ class Parser
* Parses an arbitrary path expression and defers semantical validation
* based on expected types.
*
* PathExpression ::= IdentificationVariable "." identifier [ ("." identifier)* ]
* PathExpression ::= IdentificationVariable {"." identifier}*
*
* @param integer $expectedTypes
*
@ -1553,13 +1553,14 @@ class Parser
*/
public function IdentificationVariableDeclaration()
{
$joins = array();
$rangeVariableDeclaration = $this->RangeVariableDeclaration();
$indexBy = $this->lexer->isNextToken(Lexer::T_INDEX)
? $this->IndexBy()
: null;
$rangeVariableDeclaration->isRoot = true;
$indexBy = $this->lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null;
$joins = array();
while (
$this->lexer->isNextToken(Lexer::T_LEFT) ||
$this->lexer->isNextToken(Lexer::T_INNER) ||
@ -1574,24 +1575,59 @@ class Parser
}
/**
*
* SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration
*
* {@internal WARNING: Solution is harder than a bare implementation.
* Desired EBNF support:
*
* SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable)
*
* It demands that entire SQL generation to become programmatical. This is
* needed because association based subselect requires "WHERE" conditional
* expressions to be injected, but there is no scope to do that. Only scope
* accessible is "FROM", prohibiting an easy implementation without larger
* changes.}
*
* @return \Doctrine\ORM\Query\AST\SubselectIdentificationVariableDeclaration |
* \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration
*/
public function SubselectIdentificationVariableDeclaration()
{
$this->lexer->glimpse();
/*
NOT YET IMPLEMENTED!
/* NOT YET IMPLEMENTED!
$glimpse = $this->lexer->glimpse();
if ($glimpse['type'] == Lexer::T_DOT) {
$subselectIdVarDecl = new AST\SubselectIdentificationVariableDeclaration();
$subselectIdVarDecl->associationPathExpression = $this->AssociationPathExpression();
$this->match(Lexer::T_AS);
$subselectIdVarDecl->aliasIdentificationVariable = $this->AliasIdentificationVariable();
$associationPathExpression = $this->AssociationPathExpression();
return $subselectIdVarDecl;
if ($this->lexer->isNextToken(Lexer::T_AS)) {
$this->match(Lexer::T_AS);
}
$aliasIdentificationVariable = $this->AliasIdentificationVariable();
$identificationVariable = $associationPathExpression->identificationVariable;
$field = $associationPathExpression->associationField;
$class = $this->queryComponents[$identificationVariable]['metadata'];
$targetClass = $this->em->getClassMetadata($class->associationMappings[$field]['targetEntity']);
// Building queryComponent
$joinQueryComponent = array(
'metadata' => $targetClass,
'parent' => $identificationVariable,
'relation' => $class->getAssociationMapping($field),
'map' => null,
'nestingLevel' => $this->nestingLevel,
'token' => $this->lexer->lookahead
);
$this->queryComponents[$aliasIdentificationVariable] = $joinQueryComponent;
return new AST\SubselectIdentificationVariableDeclaration(
$associationPathExpression, $aliasIdentificationVariable
);
}
*/
@ -1819,7 +1855,7 @@ class Parser
{
$token = $this->lexer->lookahead;
$peek = $this->lexer->glimpse();
if ($token['type'] === Lexer::T_OPEN_PARENTHESIS && $peek['type'] === Lexer::T_SELECT) {
$this->match(Lexer::T_OPEN_PARENTHESIS);
$expression = $this->Subselect();
@ -1827,7 +1863,7 @@ class Parser
return $expression;
}
return $this->ScalarExpression();
}
@ -2097,7 +2133,7 @@ class Parser
/**
* SelectExpression ::= (
* IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration |
* PartialObjectExpression | "(" Subselect ")" | CaseExpression
* PartialObjectExpression | "(" Subselect ")" | CaseExpression | NewObjectExpression
* ) [["AS"] ["HIDDEN"] AliasResultVariable]
*
* @return \Doctrine\ORM\Query\AST\SelectExpression
@ -3160,7 +3196,7 @@ class Parser
case $this->lexer->isNextToken(Lexer::T_COALESCE):
$expr = $this->CoalesceExpression();
break;
case $this->isAggregateFunction($this->lexer->lookahead['type']):
$expr = $this->AggregateExpression();
break;
@ -3346,7 +3382,10 @@ class Parser
* "ABS" "(" SimpleArithmeticExpression ")" |
* "SQRT" "(" SimpleArithmeticExpression ")" |
* "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
* "SIZE" "(" CollectionValuedPathExpression ")"
* "SIZE" "(" CollectionValuedPathExpression ")" |
* "DATE_DIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")" |
* "BIT_AND" "(" ArithmeticPrimary "," ArithmeticPrimary ")" |
* "BIT_OR" "(" ArithmeticPrimary "," ArithmeticPrimary ")"
*
* @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
*/
@ -3377,7 +3416,12 @@ class Parser
}
/**
* FunctionsReturningDateTime ::= "CURRENT_DATE" | "CURRENT_TIME" | "CURRENT_TIMESTAMP"
* FunctionsReturningDateTime ::=
* "CURRENT_DATE" |
* "CURRENT_TIME" |
* "CURRENT_TIMESTAMP" |
* "DATE_ADD" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")" |
* "DATE_SUB" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")"
*
* @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
*/

View File

@ -803,32 +803,56 @@ class SqlWalker implements TreeWalker
$sqlParts = array();
foreach ($identificationVarDecls as $identificationVariableDecl) {
$sql = $this->walkRangeVariableDeclaration($identificationVariableDecl->rangeVariableDeclaration);
foreach ($identificationVariableDecl->joins as $join) {
$sql .= $this->walkJoin($join);
}
if ($identificationVariableDecl->indexBy) {
$alias = $identificationVariableDecl->indexBy->simpleStateFieldPathExpression->identificationVariable;
$field = $identificationVariableDecl->indexBy->simpleStateFieldPathExpression->field;
if (isset($this->scalarFields[$alias][$field])) {
$this->rsm->addIndexByScalar($this->scalarFields[$alias][$field]);
} else {
$this->rsm->addIndexBy(
$identificationVariableDecl->indexBy->simpleStateFieldPathExpression->identificationVariable,
$identificationVariableDecl->indexBy->simpleStateFieldPathExpression->field
);
}
}
$sqlParts[] = $sql;
$sqlParts[] = $this->walkIdentificationVariableDeclaration($identificationVariableDecl);
}
return ' FROM ' . implode(', ', $sqlParts);
}
/**
* Walks down a IdentificationVariableDeclaration AST node, thereby generating the appropriate SQL.
*
* @param AST\IdentificationVariableDeclaration $identificationVariableDecl
*
* @return string
*/
public function walkIdentificationVariableDeclaration($identificationVariableDecl)
{
$sql = $this->walkRangeVariableDeclaration($identificationVariableDecl->rangeVariableDeclaration);
if ($identificationVariableDecl->indexBy) {
$this->walkIndexBy($identificationVariableDecl->indexBy);
}
foreach ($identificationVariableDecl->joins as $join) {
$sql .= $this->walkJoin($join);
}
return $sql;
}
/**
* Walks down a IndexBy AST node.
*
* @param AST\IndexBy $indexBy
*
* @return void
*/
public function walkIndexBy($indexBy)
{
$pathExpression = $indexBy->simpleStateFieldPathExpression;
$alias = $pathExpression->identificationVariable;
$field = $pathExpression->field;
if (isset($this->scalarFields[$alias][$field])) {
$this->rsm->addIndexByScalar($this->scalarFields[$alias][$field]);
return;
}
$this->rsm->addIndexBy($alias, $field);
}
/**
* Walks down a RangeVariableDeclaration AST node, thereby generating the appropriate SQL.
*
@ -1017,10 +1041,7 @@ class SqlWalker implements TreeWalker
// 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
);
$this->walkIndexBy($indexBy);
} else if (isset($relation['indexBy'])) {
$this->rsm->addIndexBy($joinedDqlAlias, $relation['indexBy']);
}
@ -1456,16 +1477,10 @@ class SqlWalker implements TreeWalker
public function walkSubselectFromClause($subselectFromClause)
{
$identificationVarDecls = $subselectFromClause->identificationVariableDeclarations;
$sqlParts = array ();
$sqlParts = array ();
foreach ($identificationVarDecls as $subselectIdVarDecl) {
$sql = $this->walkRangeVariableDeclaration($subselectIdVarDecl->rangeVariableDeclaration);
foreach ($subselectIdVarDecl->joins as $join) {
$sql .= $this->walkJoin($join);
}
$sqlParts[] = $sql;
$sqlParts[] = $this->walkIdentificationVariableDeclaration($subselectIdVarDecl);
}
return ' FROM ' . implode(', ', $sqlParts);
@ -1520,7 +1535,7 @@ class SqlWalker implements TreeWalker
$qComp = $this->queryComponents[$dqlAlias];
$class = $qComp['metadata'];
$fieldType = $class->getTypeOfField($fieldName);
$sqlSelectExpressions[] = trim($e->dispatch($this)) . ' AS ' . $columnAlias;
break;

View File

@ -74,37 +74,40 @@ class CustomTreeWalkerJoin extends Query\TreeWalkerAdapter
public function walkSelectStatement(Query\AST\SelectStatement $selectStatement)
{
foreach ($selectStatement->fromClause->identificationVariableDeclarations as $identificationVariableDeclaration) {
if ($identificationVariableDeclaration->rangeVariableDeclaration->abstractSchemaName == 'Doctrine\Tests\Models\CMS\CmsUser') {
$identificationVariableDeclaration->joins[] = new Query\AST\Join(
Query\AST\Join::JOIN_TYPE_LEFT,
new Query\AST\JoinAssociationDeclaration(
new Query\AST\JoinAssociationPathExpression(
$identificationVariableDeclaration->rangeVariableDeclaration->aliasIdentificationVariable,
'address'
),
$identificationVariableDeclaration->rangeVariableDeclaration->aliasIdentificationVariable . 'a',
null
)
);
$selectStatement->selectClause->selectExpressions[] =
new Query\AST\SelectExpression(
$identificationVariableDeclaration->rangeVariableDeclaration->aliasIdentificationVariable . 'a',
null,
false
);
$meta1 = $this->_getQuery()->getEntityManager()->getClassMetadata('Doctrine\Tests\Models\CMS\CmsUser');
$meta = $this->_getQuery()->getEntityManager()->getClassMetadata('Doctrine\Tests\Models\CMS\CmsAddress');
$this->setQueryComponent($identificationVariableDeclaration->rangeVariableDeclaration->aliasIdentificationVariable . 'a',
array(
'metadata' => $meta,
'parent' => $identificationVariableDeclaration->rangeVariableDeclaration->aliasIdentificationVariable,
'relation' => $meta1->getAssociationMapping('address'),
'map' => null,
'nestingLevel' => 0,
'token' => null
)
);
$rangeVariableDecl = $identificationVariableDeclaration->rangeVariableDeclaration;
if ($rangeVariableDecl->abstractSchemaName !== 'Doctrine\Tests\Models\CMS\CmsUser') {
continue;
}
$this->modifySelectStatement($selectStatement, $identificationVariableDeclaration);
}
}
private function modifySelectStatement(Query\AST\SelectStatement $selectStatement, $identificationVariableDecl)
{
$rangeVariableDecl = $identificationVariableDecl->rangeVariableDeclaration;
$joinAssocPathExpression = new Query\AST\JoinAssociationPathExpression($rangeVariableDecl->aliasIdentificationVariable, 'address');
$joinAssocDeclaration = new Query\AST\JoinAssociationDeclaration($joinAssocPathExpression, $rangeVariableDecl->aliasIdentificationVariable . 'a', null);
$join = new Query\AST\Join(Query\AST\Join::JOIN_TYPE_LEFT, $joinAssocDeclaration);
$selectExpression = new Query\AST\SelectExpression($rangeVariableDecl->aliasIdentificationVariable . 'a', null, false);
$identificationVariableDecl->joins[] = $join;
$selectStatement->selectClause->selectExpressions[] = $selectExpression;
$entityManager = $this->_getQuery()->getEntityManager();
$userMetadata = $entityManager->getClassMetadata('Doctrine\Tests\Models\CMS\CmsUser');
$addressMetadata = $entityManager->getClassMetadata('Doctrine\Tests\Models\CMS\CmsAddress');
$this->setQueryComponent($rangeVariableDecl->aliasIdentificationVariable . 'a',
array(
'metadata' => $addressMetadata,
'parent' => $rangeVariableDecl->aliasIdentificationVariable,
'relation' => $userMetadata->getAssociationMapping('address'),
'map' => null,
'nestingLevel' => 0,
'token' => null,
)
);
}
}

View File

@ -198,37 +198,40 @@ class CustomTreeWalkerJoin extends Query\TreeWalkerAdapter
public function walkSelectStatement(Query\AST\SelectStatement $selectStatement)
{
foreach ($selectStatement->fromClause->identificationVariableDeclarations as $identificationVariableDeclaration) {
if ($identificationVariableDeclaration->rangeVariableDeclaration->abstractSchemaName == 'Doctrine\Tests\Models\CMS\CmsUser') {
$identificationVariableDeclaration->joins[] = new Query\AST\Join(
Query\AST\Join::JOIN_TYPE_LEFT,
new Query\AST\JoinAssociationDeclaration(
new Query\AST\JoinAssociationPathExpression(
$identificationVariableDeclaration->rangeVariableDeclaration->aliasIdentificationVariable,
'address'
),
$identificationVariableDeclaration->rangeVariableDeclaration->aliasIdentificationVariable . 'a',
null
)
);
$selectStatement->selectClause->selectExpressions[] =
new Query\AST\SelectExpression(
$identificationVariableDeclaration->rangeVariableDeclaration->aliasIdentificationVariable . 'a',
null,
false
);
$meta1 = $this->_getQuery()->getEntityManager()->getClassMetadata('Doctrine\Tests\Models\CMS\CmsUser');
$meta = $this->_getQuery()->getEntityManager()->getClassMetadata('Doctrine\Tests\Models\CMS\CmsAddress');
$this->setQueryComponent($identificationVariableDeclaration->rangeVariableDeclaration->aliasIdentificationVariable . 'a',
array(
'metadata' => $meta,
'parent' => $identificationVariableDeclaration->rangeVariableDeclaration->aliasIdentificationVariable,
'relation' => $meta1->getAssociationMapping('address'),
'map' => null,
'nestingLevel' => 0,
'token' => null
)
);
$rangeVariableDecl = $identificationVariableDeclaration->rangeVariableDeclaration;
if ($rangeVariableDecl->abstractSchemaName !== 'Doctrine\Tests\Models\CMS\CmsUser') {
continue;
}
$this->modifySelectStatement($selectStatement, $identificationVariableDeclaration);
}
}
}
private function modifySelectStatement(Query\AST\SelectStatement $selectStatement, $identificationVariableDecl)
{
$rangeVariableDecl = $identificationVariableDecl->rangeVariableDeclaration;
$joinAssocPathExpression = new Query\AST\JoinAssociationPathExpression($rangeVariableDecl->aliasIdentificationVariable, 'address');
$joinAssocDeclaration = new Query\AST\JoinAssociationDeclaration($joinAssocPathExpression, $rangeVariableDecl->aliasIdentificationVariable . 'a', null);
$join = new Query\AST\Join(Query\AST\Join::JOIN_TYPE_LEFT, $joinAssocDeclaration);
$selectExpression = new Query\AST\SelectExpression($rangeVariableDecl->aliasIdentificationVariable . 'a', null, false);
$identificationVariableDecl->joins[] = $join;
$selectStatement->selectClause->selectExpressions[] = $selectExpression;
$entityManager = $this->_getQuery()->getEntityManager();
$userMetadata = $entityManager->getClassMetadata('Doctrine\Tests\Models\CMS\CmsUser');
$addressMetadata = $entityManager->getClassMetadata('Doctrine\Tests\Models\CMS\CmsAddress');
$this->setQueryComponent($rangeVariableDecl->aliasIdentificationVariable . 'a',
array(
'metadata' => $addressMetadata,
'parent' => $rangeVariableDecl->aliasIdentificationVariable,
'relation' => $userMetadata->getAssociationMapping('address'),
'map' => null,
'nestingLevel' => 0,
'token' => null,
)
);
}
}