Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document QueryComponent array shape #9527

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions lib/Doctrine/ORM/Query/AST/Functions/IdentityFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Doctrine\ORM\Query\QueryException;
use Doctrine\ORM\Query\SqlWalker;

use function assert;
use function reset;
use function sprintf;

Expand All @@ -23,22 +24,22 @@ class IdentityFunction extends FunctionNode
/** @var PathExpression */
public $pathExpression;

/** @var string */
/** @var string|null */
public $fieldMapping;

/**
* {@inheritdoc}
*/
public function getSql(SqlWalker $sqlWalker)
{
$platform = $sqlWalker->getEntityManager()->getConnection()->getDatabasePlatform();
$quoteStrategy = $sqlWalker->getEntityManager()->getConfiguration()->getQuoteStrategy();
assert($this->pathExpression->field !== null);
$entityManager = $sqlWalker->getEntityManager();
$platform = $entityManager->getConnection()->getDatabasePlatform();
$quoteStrategy = $entityManager->getConfiguration()->getQuoteStrategy();
$dqlAlias = $this->pathExpression->identificationVariable;
$assocField = $this->pathExpression->field;
$qComp = $sqlWalker->getQueryComponent($dqlAlias);
$class = $qComp['metadata'];
$assoc = $class->associationMappings[$assocField];
$targetEntity = $sqlWalker->getEntityManager()->getClassMetadata($assoc['targetEntity']);
$assoc = $sqlWalker->getMetadataForDqlAlias($dqlAlias)->associationMappings[$assocField];
$targetEntity = $entityManager->getClassMetadata($assoc['targetEntity']);
$joinColumn = reset($assoc['joinColumns']);

if ($this->fieldMapping !== null) {
Expand All @@ -63,7 +64,7 @@ public function getSql(SqlWalker $sqlWalker)
}

// The table with the relation may be a subclass, so get the table name from the association definition
$tableName = $sqlWalker->getEntityManager()->getClassMetadata($assoc['sourceEntity'])->getTableName();
$tableName = $entityManager->getClassMetadata($assoc['sourceEntity'])->getTableName();

$tableAlias = $sqlWalker->getSQLTableAlias($tableName, $dqlAlias);
$columnName = $quoteStrategy->getJoinColumnName($joinColumn, $targetEntity, $platform);
Expand Down
15 changes: 9 additions & 6 deletions lib/Doctrine/ORM/Query/AST/Functions/SizeFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;

use function assert;

/**
* "SIZE" "(" CollectionValuedPathExpression ")"
*
Expand All @@ -27,18 +29,19 @@ class SizeFunction extends FunctionNode
*/
public function getSql(SqlWalker $sqlWalker)
{
$platform = $sqlWalker->getEntityManager()->getConnection()->getDatabasePlatform();
$quoteStrategy = $sqlWalker->getEntityManager()->getConfiguration()->getQuoteStrategy();
assert($this->collectionPathExpression->field !== null);
$entityManager = $sqlWalker->getEntityManager();
$platform = $entityManager->getConnection()->getDatabasePlatform();
$quoteStrategy = $entityManager->getConfiguration()->getQuoteStrategy();
$dqlAlias = $this->collectionPathExpression->identificationVariable;
$assocField = $this->collectionPathExpression->field;

$qComp = $sqlWalker->getQueryComponent($dqlAlias);
$class = $qComp['metadata'];
$class = $sqlWalker->getMetadataForDqlAlias($dqlAlias);
$assoc = $class->associationMappings[$assocField];
$sql = 'SELECT COUNT(*) FROM ';

if ($assoc['type'] === ClassMetadata::ONE_TO_MANY) {
$targetClass = $sqlWalker->getEntityManager()->getClassMetadata($assoc['targetEntity']);
$targetClass = $entityManager->getClassMetadata($assoc['targetEntity']);
$targetTableAlias = $sqlWalker->getSQLTableAlias($targetClass->getTableName());
$sourceTableAlias = $sqlWalker->getSQLTableAlias($class->getTableName(), $dqlAlias);

Expand All @@ -60,7 +63,7 @@ public function getSql(SqlWalker $sqlWalker)
. $sourceTableAlias . '.' . $quoteStrategy->getColumnName($class->fieldNames[$targetColumn], $class, $platform);
}
} else { // many-to-many
$targetClass = $sqlWalker->getEntityManager()->getClassMetadata($assoc['targetEntity']);
$targetClass = $entityManager->getClassMetadata($assoc['targetEntity']);

$owningAssoc = $assoc['isOwningSide'] ? $assoc : $targetClass->associationMappings[$assoc['mappedBy']];
$joinTable = $owningAssoc['joinTable'];
Expand Down
51 changes: 40 additions & 11 deletions lib/Doctrine/ORM/Query/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
use Doctrine\ORM\Query\AST\UpdateStatement;
use Doctrine\ORM\Query\AST\WhenClause;
use Doctrine\ORM\Query\AST\WhereClause;
use LogicException;
use ReflectionClass;

use function array_intersect;
Expand All @@ -84,6 +85,16 @@
/**
* An LL(*) recursive-descent parser for the context-free grammar of the Doctrine Query Language.
* Parses a DQL query, reports any errors in it, and generates an AST.
*
* @psalm-type QueryComponent = array{
* metadata?: ClassMetadata<object>,
* parent?: string|null,
* relation?: mixed[]|null,
* map?: string|null,
* resultVariable?: AST\Node|string,
* nestingLevel: int,
* token: array
* }
*/
class Parser
{
Expand Down Expand Up @@ -143,16 +154,16 @@ class Parser
/** @psalm-var list<array{token: mixed, expression: mixed, nestingLevel: int}> */
private $deferredIdentificationVariables = [];

/** @psalm-var list<array{token: mixed, expression: mixed, nestingLevel: int}> */
/** @psalm-var list<array{token: mixed, expression: AST\PartialObjectExpression, nestingLevel: int}> */
private $deferredPartialObjectExpressions = [];

/** @psalm-var list<array{token: mixed, expression: mixed, nestingLevel: int}> */
/** @psalm-var list<array{token: mixed, expression: AST\PathExpression, nestingLevel: int}> */
private $deferredPathExpressions = [];

/** @psalm-var list<array{token: mixed, expression: mixed, nestingLevel: int}> */
private $deferredResultVariables = [];

/** @psalm-var list<array{token: mixed, expression: mixed, nestingLevel: int}> */
/** @psalm-var list<array{token: mixed, expression: AST\NewObjectExpression, nestingLevel: int}> */
private $deferredNewObjectExpressions = [];

/**
Expand Down Expand Up @@ -186,7 +197,7 @@ class Parser
/**
* Map of declared query components in the parsed query.
*
* @psalm-var array<string, array<string, mixed>>
* @psalm-var array<string, QueryComponent>
*/
private $queryComponents = [];

Expand Down Expand Up @@ -503,6 +514,7 @@ public function syntaxError($expected = '', $token = null)
* @psalm-param array<string, mixed>|null $token
*
* @return void
* @psalm-return no-return
*
* @throws QueryException
*/
Expand Down Expand Up @@ -709,7 +721,7 @@ private function processDeferredPartialObjectExpressions(): void
{
foreach ($this->deferredPartialObjectExpressions as $deferredItem) {
$expr = $deferredItem['expression'];
$class = $this->queryComponents[$expr->identificationVariable]['metadata'];
$class = $this->getMetadataForDqlAlias($expr->identificationVariable);

foreach ($expr->partialFieldSet as $field) {
if (isset($class->fieldMappings[$field])) {
Expand Down Expand Up @@ -791,8 +803,7 @@ private function processDeferredPathExpressions(): void
foreach ($this->deferredPathExpressions as $deferredItem) {
$pathExpression = $deferredItem['expression'];

$qComp = $this->queryComponents[$pathExpression->identificationVariable];
$class = $qComp['metadata'];
$class = $this->getMetadataForDqlAlias($pathExpression->identificationVariable);

$field = $pathExpression->field;
if ($field === null) {
Expand Down Expand Up @@ -860,7 +871,7 @@ private function processRootEntityAliasSelected(): void
}

foreach ($this->identVariableExpressions as $dqlAlias => $expr) {
if (isset($this->queryComponents[$dqlAlias]) && $this->queryComponents[$dqlAlias]['parent'] === null) {
if (isset($this->queryComponents[$dqlAlias]) && ! isset($this->queryComponents[$dqlAlias]['parent'])) {
return;
}
}
Expand Down Expand Up @@ -1096,11 +1107,11 @@ public function JoinAssociationPathExpression()
$this->match(Lexer::T_DOT);
$this->match(Lexer::T_IDENTIFIER);

assert($this->lexer->token !== null);
$field = $this->lexer->token['value'];

// Validate association field
$qComp = $this->queryComponents[$identVariable];
$class = $qComp['metadata'];
$class = $this->getMetadataForDqlAlias($identVariable);

if (! $class->hasAssociation($field)) {
$this->semanticalError('Class ' . $class->name . ' has no association named ' . $field);
Expand Down Expand Up @@ -1263,6 +1274,7 @@ public function SimpleSelectClause()
public function UpdateClause()
{
$this->match(Lexer::T_UPDATE);
assert($this->lexer->lookahead !== null);

$token = $this->lexer->lookahead;
$abstractSchemaName = $this->AbstractSchemaName();
Expand Down Expand Up @@ -1319,6 +1331,7 @@ public function DeleteClause()
$this->match(Lexer::T_FROM);
}

assert($this->lexer->lookahead !== null);
$token = $this->lexer->lookahead;
$abstractSchemaName = $this->AbstractSchemaName();

Expand Down Expand Up @@ -1787,6 +1800,7 @@ public function RangeVariableDeclaration()
$this->match(Lexer::T_AS);
}

assert($this->lexer->lookahead !== null);
$token = $this->lexer->lookahead;
$aliasIdentificationVariable = $this->AliasIdentificationVariable();
$classMetadata = $this->em->getClassMetadata($abstractSchemaName);
Expand Down Expand Up @@ -1819,13 +1833,15 @@ public function JoinAssociationDeclaration()
$this->match(Lexer::T_AS);
}

assert($this->lexer->lookahead !== null);

$aliasIdentificationVariable = $this->AliasIdentificationVariable();
$indexBy = $this->lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null;

$identificationVariable = $joinAssociationPathExpression->identificationVariable;
$field = $joinAssociationPathExpression->associationField;

$class = $this->queryComponents[$identificationVariable]['metadata'];
$class = $this->getMetadataForDqlAlias($identificationVariable);
$targetClass = $this->em->getClassMetadata($class->associationMappings[$field]['targetEntity']);

// Building queryComponent
Expand Down Expand Up @@ -1897,6 +1913,7 @@ public function PartialObjectExpression()

$partialObjectExpression = new AST\PartialObjectExpression($identificationVariable, $partialFieldSet);

assert($this->lexer->token !== null);
// Defer PartialObjectExpression validation
$this->deferredPartialObjectExpressions[] = [
'expression' => $partialObjectExpression,
Expand Down Expand Up @@ -2330,6 +2347,8 @@ public function SelectExpression()
$aliasResultVariable = null;

if ($mustHaveAliasResultVariable || $this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
assert($this->lexer->lookahead !== null);
assert($expression instanceof AST\Node || is_string($expression));
$token = $this->lexer->lookahead;
$aliasResultVariable = $this->AliasResultVariable();

Expand Down Expand Up @@ -2426,6 +2445,7 @@ public function SimpleSelectExpression()
}

if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
assert($this->lexer->lookahead !== null);
$token = $this->lexer->lookahead;
$resultVariable = $this->AliasResultVariable();
$expr->fieldIdentificationVariable = $resultVariable;
Expand Down Expand Up @@ -3621,4 +3641,13 @@ public function CustomFunctionsReturningStrings()

return $function;
}

private function getMetadataForDqlAlias(string $dqlAlias): ClassMetadata
{
if (! isset($this->queryComponents[$dqlAlias]['metadata'])) {
throw new LogicException(sprintf('No metadata for DQL alias: %s', $dqlAlias));
SenseException marked this conversation as resolved.
Show resolved Hide resolved
}

return $this->queryComponents[$dqlAlias]['metadata'];
}
}
Loading