diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index 48b13df64a..d52f3ce1b2 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -2924,6 +2924,10 @@ public function StringPrimary() case Lexer::T_COALESCE: case Lexer::T_NULLIF: return $this->CaseExpression(); + default: + if ($this->isAggregateFunction($lookaheadType)) { + return $this->AggregateExpression(); + } } $this->syntaxError( diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/GH7286Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH7286Test.php new file mode 100644 index 0000000000..75e626f336 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH7286Test.php @@ -0,0 +1,140 @@ +setUpEntitySchema( + [ + GH7286Entity::class, + ] + ); + + $this->_em->persist(new GH7286Entity('foo', 1)); + $this->_em->persist(new GH7286Entity('foo', 2)); + $this->_em->persist(new GH7286Entity('bar', 3)); + $this->_em->persist(new GH7286Entity(null, 4)); + $this->_em->flush(); + $this->_em->clear(); + } + + public function testAggregateExpressionInFunction() : void + { + $query = $this->_em->createQuery( + 'SELECT CONCAT(e.type, MIN(e.version)) pair' + . ' FROM ' . GH7286Entity::class . ' e' + . ' WHERE e.type IS NOT NULL' + . ' GROUP BY e.type' + . ' ORDER BY e.type' + ); + + self::assertSame( + [ + ['pair' => 'bar3'], + ['pair' => 'foo1'], + ], + $query->getArrayResult() + ); + } + + /** + * @group DDC-1091 + */ + public function testAggregateFunctionInCustomFunction() : void + { + $this->_em->getConfiguration()->addCustomStringFunction('CC', GH7286CustomConcat::class); + + $entityA = new GH7286Entity('foo', 1); + $this->_em->persist($entityA); + $this->_em->flush(); + $this->_em->clear(); + + $query = $this->_em->createQuery( + 'SELECT CC(e.type, MIN(e.version)) pair' + . ' FROM ' . GH7286Entity::class . ' e' + . ' WHERE e.type IS NOT NULL AND e.type != :type' + . ' GROUP BY e.type' + ); + $query->setParameter('type', 'bar'); + + self::assertSame( + ['pair' => 'foo1'], + $query->getSingleResult() + ); + } +} + +/** + * @Entity + */ +class GH7286Entity +{ + /** + * @Id + * @Column(type="integer") + * @GeneratedValue + * @var int + */ + public $id; + + /** + * @Column(nullable=true) + * @var string|null + */ + public $type; + + /** + * @Column(type="integer") + * @var int + */ + public $version; + + public function __construct(?string $type, int $version) + { + $this->type = $type; + $this->version = $version; + } +} + +class GH7286CustomConcat extends FunctionNode +{ + /** @var Node */ + private $first; + + /** @var Node */ + private $second; + + public function parse(Parser $parser) : void + { + $parser->match(Lexer::T_IDENTIFIER); + $parser->match(Lexer::T_OPEN_PARENTHESIS); + + $this->first = $parser->StringPrimary(); + $parser->match(Lexer::T_COMMA); + $this->second = $parser->StringPrimary(); + + $parser->match(Lexer::T_CLOSE_PARENTHESIS); + } + + public function getSql(SqlWalker $walker) : string + { + return $walker->getConnection()->getDatabasePlatform()->getConcatExpression( + $this->first->dispatch($walker), + $this->second->dispatch($walker) + ); + } +} diff --git a/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php b/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php index 064c0a799c..fe3b298cbf 100644 --- a/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php +++ b/tests/Doctrine/Tests/ORM/Query/LanguageRecognitionTest.php @@ -708,6 +708,13 @@ public function testNewLiteralWithSubselectExpression() { $this->assertValidDQL("SELECT new " . __NAMESPACE__ . "\\DummyStruct(u.id, 'foo', (SELECT 1 FROM Doctrine\Tests\Models\CMS\CmsUser su), true) FROM Doctrine\Tests\Models\CMS\CmsUser u"); } + + public function testStringPrimaryAcceptsAggregateExpression() : void + { + $this->assertValidDQL( + 'SELECT CONCAT(a.topic, MAX(a.version)) last FROM Doctrine\Tests\Models\CMS\CmsArticle a GROUP BY a' + ); + } } /** @Entity */