1
0
mirror of synced 2025-03-06 04:46:13 +03:00

No longer treat aggregate functions as a special case.

This commit is contained in:
Mathew Davies 2017-06-09 15:09:41 +01:00 committed by Luís Cobucci
parent e4ff7a35a8
commit 866418e40f
No known key found for this signature in database
GPG Key ID: EC61C5F01750ED3C
11 changed files with 295 additions and 79 deletions

View File

@ -425,10 +425,6 @@ class Configuration extends \Doctrine\DBAL\Configuration
*/
public function addCustomStringFunction($name, $className)
{
if (Query\Parser::isInternalFunction($name)) {
throw ORMException::overwriteInternalDQLFunctionNotAllowed($name);
}
$this->_attributes['customStringFunctions'][strtolower($name)] = $className;
}
@ -483,10 +479,6 @@ class Configuration extends \Doctrine\DBAL\Configuration
*/
public function addCustomNumericFunction($name, $className)
{
if (Query\Parser::isInternalFunction($name)) {
throw ORMException::overwriteInternalDQLFunctionNotAllowed($name);
}
$this->_attributes['customNumericFunctions'][strtolower($name)] = $className;
}
@ -541,10 +533,6 @@ class Configuration extends \Doctrine\DBAL\Configuration
*/
public function addCustomDatetimeFunction($name, $className)
{
if (Query\Parser::isInternalFunction($name)) {
throw ORMException::overwriteInternalDQLFunctionNotAllowed($name);
}
$this->_attributes['customDatetimeFunctions'][strtolower($name)] = $className;
}

View File

@ -323,16 +323,6 @@ class ORMException extends Exception
);
}
/**
* @param string $functionName
*
* @return ORMException
*/
public static function overwriteInternalDQLFunctionNotAllowed($functionName)
{
return new self("It is not allowed to overwrite internal function '$functionName' in the DQL parser through user-defined functions.");
}
/**
* @return ORMException
*/

View File

@ -0,0 +1,55 @@
<?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\Functions;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\AST\AggregateExpression;
/**
* "AVG" "(" ["DISTINCT"] StringPrimary ")"
*
* @link www.doctrine-project.org
* @since 2.0
* @author Mathew Davies <thepixeldeveloper@icloud.com>
*/
class AvgFunction extends FunctionNode
{
/**
* @var AggregateExpression
*/
public $aggregateExpression;
/**
* @inheritDoc
*/
public function getSql(SqlWalker $sqlWalker)
{
return $this->aggregateExpression->dispatch($sqlWalker);
}
/**
* @inheritDoc
*/
public function parse(Parser $parser)
{
$this->aggregateExpression = $parser->AggregateExpression();
}
}

View File

@ -0,0 +1,55 @@
<?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\Functions;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\AST\AggregateExpression;
/**
* "COUNT" "(" ["DISTINCT"] StringPrimary ")"
*
* @link www.doctrine-project.org
* @since 2.0
* @author Mathew Davies <thepixeldeveloper@icloud.com>
*/
class CountFunction extends FunctionNode
{
/**
* @var AggregateExpression
*/
public $aggregateExpression;
/**
* @inheritDoc
*/
public function getSql(SqlWalker $sqlWalker)
{
return $this->aggregateExpression->dispatch($sqlWalker);
}
/**
* @inheritDoc
*/
public function parse(Parser $parser)
{
$this->aggregateExpression = $parser->AggregateExpression();
}
}

View File

@ -0,0 +1,55 @@
<?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\Functions;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\AST\AggregateExpression;
/**
* "MAX" "(" ["DISTINCT"] StringPrimary ")"
*
* @link www.doctrine-project.org
* @since 2.0
* @author Mathew Davies <thepixeldeveloper@icloud.com>
*/
class MaxFunction extends FunctionNode
{
/**
* @var AggregateExpression
*/
public $aggregateExpression;
/**
* @inheritDoc
*/
public function getSql(SqlWalker $sqlWalker)
{
return $this->aggregateExpression->dispatch($sqlWalker);
}
/**
* @inheritDoc
*/
public function parse(Parser $parser)
{
$this->aggregateExpression = $parser->AggregateExpression();
}
}

View File

@ -0,0 +1,55 @@
<?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\Functions;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\AST\AggregateExpression;
/**
* "MIN" "(" ["DISTINCT"] StringPrimary ")"
*
* @link www.doctrine-project.org
* @since 2.0
* @author Mathew Davies <thepixeldeveloper@icloud.com>
*/
class MinFunction extends FunctionNode
{
/**
* @var AggregateExpression
*/
public $aggregateExpression;
/**
* @inheritDoc
*/
public function getSql(SqlWalker $sqlWalker)
{
return $this->aggregateExpression->dispatch($sqlWalker);
}
/**
* @inheritDoc
*/
public function parse(Parser $parser)
{
$this->aggregateExpression = $parser->AggregateExpression();
}
}

View File

@ -0,0 +1,55 @@
<?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\Functions;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\AST\AggregateExpression;
/**
* "SUM" "(" ["DISTINCT"] StringPrimary ")"
*
* @link www.doctrine-project.org
* @since 2.0
* @author Mathew Davies <thepixeldeveloper@icloud.com>
*/
class SumFunction extends FunctionNode
{
/**
* @var AggregateExpression
*/
public $aggregateExpression;
/**
* @inheritDoc
*/
public function getSql(SqlWalker $sqlWalker)
{
return $this->aggregateExpression->dispatch($sqlWalker);
}
/**
* @inheritDoc
*/
public function parse(Parser $parser)
{
$this->aggregateExpression = $parser->AggregateExpression();
}
}

View File

@ -65,6 +65,13 @@ class Parser
'date_diff' => Functions\DateDiffFunction::class,
'bit_and' => Functions\BitAndFunction::class,
'bit_or' => Functions\BitOrFunction::class,
// Aggregate functions
'min' => Functions\MinFunction::class,
'max' => Functions\MaxFunction::class,
'avg' => Functions\AvgFunction::class,
'sum' => Functions\SumFunction::class,
'count' => Functions\CountFunction::class,
];
/**
@ -171,23 +178,6 @@ class Parser
*/
private $identVariableExpressions = [];
/**
* Checks if a function is internally defined. Used to prevent overwriting
* of built-in functions through user-defined functions.
*
* @param string $functionName
*
* @return bool
*/
static public function isInternalFunction($functionName)
{
$functionName = strtolower($functionName);
return isset(self::$_STRING_FUNCTIONS[$functionName])
|| isset(self::$_DATETIME_FUNCTIONS[$functionName])
|| isset(self::$_NUMERIC_FUNCTIONS[$functionName]);
}
/**
* Creates a new query parser object.
*
@ -1978,9 +1968,6 @@ class Parser
// SUM(u.id) + COUNT(u.id)
return $this->SimpleArithmeticExpression();
case ($this->isAggregateFunction($this->lexer->lookahead['type'])):
return $this->AggregateExpression();
default:
// IDENTITY(u)
return $this->FunctionDeclaration();
@ -2209,11 +2196,6 @@ class Parser
$expression = $this->ScalarExpression();
break;
case ($this->isAggregateFunction($lookaheadType)):
// COUNT(u.id)
$expression = $this->AggregateExpression();
break;
default:
// IDENTITY(u)
$expression = $this->FunctionDeclaration();
@ -2858,10 +2840,6 @@ class Parser
$peek = $this->lexer->glimpse();
if ($peek['value'] == '(') {
if ($this->isAggregateFunction($this->lexer->lookahead['type'])) {
return $this->AggregateExpression();
}
return $this->FunctionDeclaration();
}
@ -2932,11 +2910,6 @@ class Parser
case Lexer::T_COALESCE:
case Lexer::T_NULLIF:
return $this->CaseExpression();
default:
if ($this->isAggregateFunction($lookaheadType)) {
return $this->AggregateExpression();
}
}
$this->syntaxError(
@ -3236,10 +3209,6 @@ class Parser
$expr = $this->CoalesceExpression();
break;
case $this->isAggregateFunction($this->lexer->lookahead['type']):
$expr = $this->AggregateExpression();
break;
case $this->isFunction():
$expr = $this->FunctionDeclaration();
break;

View File

@ -1579,10 +1579,14 @@ class SqlWalker implements TreeWalker
$sql .= $this->walkPathExpression($expr);
break;
case ($expr instanceof AST\AggregateExpression):
case ($expr instanceof AST\Functions\AvgFunction):
case ($expr instanceof AST\Functions\CountFunction):
case ($expr instanceof AST\Functions\MaxFunction):
case ($expr instanceof AST\Functions\MinFunction):
case ($expr instanceof AST\Functions\SumFunction):
$alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
$sql .= $this->walkAggregateExpression($expr) . ' AS dctrn__' . $alias;
$sql .= $expr->dispatch($this) . ' AS dctrn__' . $alias;
break;
case ($expr instanceof AST\Subselect):

View File

@ -265,8 +265,6 @@ class ConfigurationTest extends TestCase
$this->assertSame(null, $this->configuration->getCustomStringFunction('NonExistingFunction'));
$this->configuration->setCustomStringFunctions(['OtherFunctionName' => __CLASS__]);
$this->assertSame(__CLASS__, $this->configuration->getCustomStringFunction('OtherFunctionName'));
$this->expectException(ORMException::class);
$this->configuration->addCustomStringFunction('concat', __CLASS__);
}
public function testAddGetCustomNumericFunction()
@ -276,8 +274,6 @@ class ConfigurationTest extends TestCase
$this->assertSame(null, $this->configuration->getCustomNumericFunction('NonExistingFunction'));
$this->configuration->setCustomNumericFunctions(['OtherFunctionName' => __CLASS__]);
$this->assertSame(__CLASS__, $this->configuration->getCustomNumericFunction('OtherFunctionName'));
$this->expectException(ORMException::class);
$this->configuration->addCustomNumericFunction('abs', __CLASS__);
}
public function testAddGetCustomDatetimeFunction()
@ -287,8 +283,6 @@ class ConfigurationTest extends TestCase
$this->assertSame(null, $this->configuration->getCustomDatetimeFunction('NonExistingFunction'));
$this->configuration->setCustomDatetimeFunctions(['OtherFunctionName' => __CLASS__]);
$this->assertSame(__CLASS__, $this->configuration->getCustomDatetimeFunction('OtherFunctionName'));
$this->expectException(ORMException::class);
$this->configuration->addCustomDatetimeFunction('date_add', __CLASS__);
}
public function testAddGetCustomHydrationMode()

View File

@ -58,12 +58,11 @@ class CustomFunctionsTest extends OrmFunctionalTestCase
$this->_em->getConfiguration()->addCustomStringFunction('COUNT', 'Doctrine\Tests\ORM\Functional\CustomCount');
$query = $this->_em->createQuery('SELECT COUNT(u.id) FROM Doctrine\Tests\Models\CMS\CmsUser u');
$query = $this->_em->createQuery('SELECT COUNT(DISTINCT u.id) FROM Doctrine\Tests\Models\CMS\CmsUser u');
$users = $query->getResult();
$usersCount = $query->getSingleScalarResult();
$this->assertEquals(1, count($users));
$this->assertSame($user, $users[0]);
$this->assertEquals(1, $usersCount);
}
}
@ -91,20 +90,17 @@ class NoOp extends FunctionNode
class CustomCount extends FunctionNode
{
/**
* @var PathExpression
* @var Query\AST\AggregateExpression
*/
private $field;
private $aggregateExpression;
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->field = $parser->StringExpression();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$this->aggregateExpression = $parser->AggregateExpression();
}
public function getSql(SqlWalker $sqlWalker)
{
return $this->field->dispatch($sqlWalker);
return $this->aggregateExpression->dispatch($sqlWalker);
}
}