1
0
mirror of synced 2024-12-14 07:06:04 +03:00

Fixed DDC-1236: GROUP BY now supports ResultVariable and IdentificationVariable. Composite PK is also supported. If you are willing to group by an aggregate function or a function itself, just place it in SELECT expression then refer to it in the GROUP BY clause. If you are not willing to have the function being part of your resultset, just mark the column as HIDDEN and you are done.

This commit is contained in:
Guilherme Blanco 2011-12-01 23:52:35 -05:00
parent 619a31913a
commit 2642daa438
6 changed files with 152 additions and 58 deletions

View File

@ -2,7 +2,7 @@
/*
* 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
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHARNTABILITY 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
@ -1305,7 +1305,7 @@ class Parser
}
/**
* GroupByItem ::= IdentificationVariable | SingleValuedPathExpression
* GroupByItem ::= IdentificationVariable | ResultVariable | SingleValuedPathExpression
*
* @return string | \Doctrine\ORM\Query\AST\PathExpression
*/
@ -1314,18 +1314,20 @@ class Parser
// We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
$glimpse = $this->_lexer->glimpse();
if ($glimpse['type'] == Lexer::T_DOT) {
if ($glimpse['type'] === Lexer::T_DOT) {
return $this->SingleValuedPathExpression();
}
$token = $this->_lexer->lookahead;
$identVariable = $this->IdentificationVariable();
// Still need to decide between IdentificationVariable or ResultVariable
$lookaheadValue = $this->_lexer->lookahead['value'];
if ( ! isset($this->_queryComponents[$identVariable])) {
$this->semanticalError('Cannot group by undefined identification variable.');
if ( ! isset($this->_queryComponents[$lookaheadValue])) {
$this->semanticalError('Cannot group by undefined identification or result variable.');
}
return $identVariable;
return (isset($this->_queryComponents[$lookaheadValue]['metadata']))
? $this->IdentificationVariable()
: $this->ResultVariable();
}
/**

View File

@ -1014,6 +1014,8 @@ class SqlWalker implements TreeWalker
$sql .= $col . ' AS ' . $columnAlias;
$this->_scalarResultAliasMap[$resultAlias] = $columnAlias;
if ( ! $hidden) {
$this->_rsm->addScalarResult($columnAlias, $resultAlias);
$this->_scalarFields[$dqlAlias][$fieldName] = $columnAlias;
@ -1103,6 +1105,8 @@ class SqlWalker implements TreeWalker
$sqlParts[] = $col . ' AS '. $columnAlias;
$this->_scalarResultAliasMap[$resultAlias][] = $columnAlias;
$this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name);
}
@ -1132,6 +1136,8 @@ class SqlWalker implements TreeWalker
$sqlParts[] = $col . ' AS ' . $columnAlias;
$this->_scalarResultAliasMap[$resultAlias][] = $columnAlias;
$this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName);
}
}
@ -1319,25 +1325,7 @@ class SqlWalker implements TreeWalker
$sqlParts = array();
foreach ($groupByClause->groupByItems AS $groupByItem) {
if ( ! is_string($groupByItem)) {
$sqlParts[] = $this->walkGroupByItem($groupByItem);
continue;
}
foreach ($this->_queryComponents[$groupByItem]['metadata']->fieldNames AS $field) {
$item = new AST\PathExpression(AST\PathExpression::TYPE_STATE_FIELD, $groupByItem, $field);
$item->type = AST\PathExpression::TYPE_STATE_FIELD;
$sqlParts[] = $this->walkGroupByItem($item);
}
foreach ($this->_queryComponents[$groupByItem]['metadata']->associationMappings AS $mapping) {
if ($mapping['isOwningSide'] && $mapping['type'] & ClassMetadataInfo::TO_ONE) {
$item = new AST\PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $groupByItem, $mapping['fieldName']);
$item->type = AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION;
$sqlParts[] = $this->walkGroupByItem($item);
}
}
}
return ' GROUP BY ' . implode(', ', $sqlParts);
@ -1349,9 +1337,38 @@ class SqlWalker implements TreeWalker
* @param GroupByItem
* @return string The SQL.
*/
public function walkGroupByItem(AST\PathExpression $pathExpr)
public function walkGroupByItem($groupByItem)
{
return $this->walkPathExpression($pathExpr);
// StateFieldPathExpression
if ( ! is_string($groupByItem)) {
return $this->walkPathExpression($groupByItem);
}
// ResultVariable
if (isset($this->_queryComponents[$groupByItem]['resultVariable'])) {
return $this->walkResultVariable($groupByItem);
}
// IdentificationVariable
$sqlParts = array();
foreach ($this->_queryComponents[$groupByItem]['metadata']->fieldNames AS $field) {
$item = new AST\PathExpression(AST\PathExpression::TYPE_STATE_FIELD, $groupByItem, $field);
$item->type = AST\PathExpression::TYPE_STATE_FIELD;
$sqlParts[] = $this->walkPathExpression($item);
}
foreach ($this->_queryComponents[$groupByItem]['metadata']->associationMappings AS $mapping) {
if ($mapping['isOwningSide'] && $mapping['type'] & ClassMetadataInfo::TO_ONE) {
$item = new AST\PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $groupByItem, $mapping['fieldName']);
$item->type = AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION;
$sqlParts[] = $this->walkPathExpression($item);
}
}
return implode(', ', $sqlParts);
}
/**
@ -1997,4 +2014,21 @@ class SqlWalker implements TreeWalker
? $this->_conn->quote($stringPrimary)
: $stringPrimary->dispatch($this);
}
/**
* Walks down a ResultVriable that represents an AST node, thereby generating the appropriate SQL.
*
* @param string $resultVariable
* @return string The SQL.
*/
public function walkResultVariable($resultVariable)
{
$resultAlias = $this->_scalarResultAliasMap[$resultVariable];
if (is_array($resultAlias)) {
return implode(', ', $resultAlias);
}
return $resultAlias;
}
}

View File

@ -168,7 +168,7 @@ interface TreeWalker
* @param GroupByItem
* @return string The SQL.
*/
function walkGroupByItem(AST\PathExpression $pathExpr);
function walkGroupByItem($groupByItem);
/**
* Walks down an UpdateStatement AST node, thereby generating the appropriate SQL.
@ -394,6 +394,14 @@ interface TreeWalker
*/
function walkPathExpression($pathExpr);
/**
* Walks down an ResultVariable AST node, thereby generating the appropriate SQL.
*
* @param string $resultVariable
* @return string The SQL.
*/
function walkResultVariable($resultVariable);
/**
* Gets an executor that can be used to execute the result of this walker.
*

View File

@ -202,7 +202,7 @@ abstract class TreeWalkerAdapter implements TreeWalker
* @param GroupByItem
* @return string The SQL.
*/
public function walkGroupByItem(AST\PathExpression $pathExpr) {}
public function walkGroupByItem($groupByItem) {}
/**
* Walks down an UpdateStatement AST node, thereby generating the appropriate SQL.
@ -428,6 +428,14 @@ abstract class TreeWalkerAdapter implements TreeWalker
*/
public function walkPathExpression($pathExpr) {}
/**
* Walks down an ResultVariable AST node, thereby generating the appropriate SQL.
*
* @param string $resultVariable
* @return string The SQL.
*/
public function walkResultVariable($resultVariable) {}
/**
* Gets an executor that can be used to execute the result of this walker.
*

View File

@ -270,10 +270,10 @@ class TreeWalkerChain implements TreeWalker
* @param GroupByItem
* @return string The SQL.
*/
public function walkGroupByItem(AST\PathExpression $pathExpr)
public function walkGroupByItem($groupByItem)
{
foreach ($this->_walkers as $walker) {
$walker->walkGroupByItem($pathExpr);
$walker->walkGroupByItem($groupByItem);
}
}
@ -641,6 +641,19 @@ class TreeWalkerChain implements TreeWalker
}
}
/**
* Walks down an ResultVariable AST node, thereby generating the appropriate SQL.
*
* @param string $resultVariable
* @return string The SQL.
*/
public function walkResultVariable($resultVariable)
{
foreach ($this->_walkers as $walker) {
$walker->walkResultVariable($resultVariable);
}
}
/**
* Gets an executor that can be used to execute the result of this walker.
*

View File

@ -40,7 +40,14 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
$query->setHint($name, $value);
}
parent::assertEquals($sqlToBeConfirmed, $query->getSQL());
$sqlGenerated = $query->getSQL();
parent::assertEquals(
$sqlToBeConfirmed,
$sqlGenerated,
sprintf('"%s" is not equal of "%s"', $sqlGenerated, $sqlToBeConfirmed)
);
$query->free();
} catch (\Exception $e) {
$this->fail($e->getMessage() ."\n".$e->getTraceAsString());
@ -1335,6 +1342,28 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
);
}
/**
* @group DDC-1236
*/
public function testGroupBySupportsResultVariable()
{
$this->assertSqlGeneration(
'SELECT u, u.status AS st FROM Doctrine\Tests\Models\CMS\CmsUser u GROUP BY st',
'SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3, c0_.status AS status4 FROM cms_users c0_ GROUP BY status4'
);
}
/**
* @group DDC-1236
*/
public function testGroupBySupportsIdentificationVariable()
{
$this->assertSqlGeneration(
'SELECT u AS user FROM Doctrine\Tests\Models\CMS\CmsUser u GROUP BY user',
'SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ GROUP BY id0, status1, username2, name3'
);
}
public function testCustomTypeValueSql()
{
if (DBALType::hasType('negative_to_positive')) {