-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix different first/max result values taking up query cache space (#1…
…1188) * Add a test covering the #11112 issue * Add new OutputWalker and SqlFinalizer interfaces * Add a SingleSelectSqlFinalizer that can take care of adding offset/limit as well as locking mode statements to a given SQL query. Add a FinalizedSelectExecutor that executes given, finalized SQL statements. * In SqlWalker, split SQL query generation into the two parts that shall happen before and after the finalization phase. Move the part that generates "pre-finalization" SQL into a dedicated method. Use a side channel in SingleSelectSqlFinalizer to access the "finalization" logic and avoid duplication. * Fix CS violations * Skip the GH11112 test while applying refactorings * Avoid a Psalm complaint due to invalid (?) docblock syntax * Establish alternate code path - queries can obtain the sql executor through the finalizer, parser knows about output walkers yielding finalizers * Remove a possibly premature comment * Re-enable the #11112 test * Fix CS * Make RootTypeWalker inherit from SqlOutputWalker so it becomes finalizer-aware * Update QueryCacheTest, since first/max results no longer need extra cache entries * Fix ParserResultSerializationTest by forcing the parser to produce a ParserResult of the old kind (with the executor already constructed) * Fix WhereInWalkerTest * Update lib/Doctrine/ORM/Query/Exec/PreparedExecutorFinalizer.php Co-authored-by: Grégoire Paris <[email protected]> * Fix tests * Fix a Psalm complaint * Fix a test * Fix CS * Make the NullSqlWalker an instance of SqlOutputWalker * Avoid multiple cache entries caused by LimitSubqueryOutputWalker * Fix Psalm complaints * Fix static analysis complaints * Remove experimental code that I committed accidentally * Remove unnecessary baseline entry * Make AddUnknownQueryComponentWalker subclass SqlOutputWalker That way, we have no remaining classes in the codebase subclassing SqlWalker but not SqlOutputWalker * Use more expressive exception classes * Add a deprecation message * Move SqlExecutor creation to ParserResult, to minimize public methods available on it * Avoid keeping the SqlExecutor in the Query, since it must be generated just in time (e. g. in case Query parameters change) * Address PHPStan complaints * Fix tests * Small refactorings * Add an upgrade notice * Small refactorings * Update the Psalm baseline * Add a missing namespace import * Update Psalm baseline * Fix CS * Fix Psalm baseline --------- Co-authored-by: Grégoire Paris <[email protected]>
- Loading branch information
Showing
23 changed files
with
591 additions
and
104 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Doctrine\ORM\Query\Exec; | ||
|
||
use Doctrine\DBAL\Connection; | ||
use Doctrine\DBAL\Result; | ||
use Doctrine\DBAL\Types\Type; | ||
|
||
/** | ||
* SQL executor for a given, final, single SELECT SQL query | ||
* | ||
* @method string getSqlStatements() | ||
*/ | ||
class FinalizedSelectExecutor extends AbstractSqlExecutor | ||
{ | ||
public function __construct(string $sql) | ||
{ | ||
parent::__construct(); | ||
|
||
$this->sqlStatements = $sql; | ||
} | ||
|
||
/** | ||
* @param list<mixed>|array<string, mixed> $params | ||
* @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types | ||
*/ | ||
public function execute(Connection $conn, array $params, array $types): Result | ||
{ | ||
return $conn->executeQuery($this->getSqlStatements(), $params, $types, $this->queryCacheProfile); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Doctrine\ORM\Query\Exec; | ||
|
||
use Doctrine\ORM\Query; | ||
|
||
/** | ||
* PreparedExecutorFinalizer is a wrapper for the SQL finalization | ||
* phase that does nothing - it is constructed with the sql executor | ||
* already. | ||
*/ | ||
final class PreparedExecutorFinalizer implements SqlFinalizer | ||
{ | ||
/** @var AbstractSqlExecutor */ | ||
private $executor; | ||
|
||
public function __construct(AbstractSqlExecutor $exeutor) | ||
{ | ||
$this->executor = $exeutor; | ||
} | ||
|
||
public function createExecutor(Query $query): AbstractSqlExecutor | ||
{ | ||
return $this->executor; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Doctrine\ORM\Query\Exec; | ||
|
||
use Doctrine\DBAL\LockMode; | ||
use Doctrine\ORM\Query; | ||
use Doctrine\ORM\Query\QueryException; | ||
use Doctrine\ORM\Utility\LockSqlHelper; | ||
|
||
/** | ||
* SingleSelectSqlFinalizer finalizes a given SQL query by applying | ||
* the query's firstResult/maxResult values as well as extra read lock/write lock | ||
* statements, both through the platform-specific methods. | ||
* | ||
* The resulting, "finalized" SQL is passed to a FinalizedSelectExecutor. | ||
*/ | ||
class SingleSelectSqlFinalizer implements SqlFinalizer | ||
{ | ||
use LockSqlHelper; | ||
|
||
/** @var string */ | ||
private $sql; | ||
|
||
public function __construct(string $sql) | ||
{ | ||
$this->sql = $sql; | ||
} | ||
|
||
/** | ||
* This method exists temporarily to support old SqlWalker interfaces. | ||
* | ||
* @internal | ||
* | ||
* @psalm-internal Doctrine\ORM | ||
*/ | ||
public function finalizeSql(Query $query): string | ||
{ | ||
$platform = $query->getEntityManager()->getConnection()->getDatabasePlatform(); | ||
|
||
$sql = $platform->modifyLimitQuery($this->sql, $query->getMaxResults(), $query->getFirstResult()); | ||
|
||
$lockMode = $query->getHint(Query::HINT_LOCK_MODE) ?: LockMode::NONE; | ||
|
||
if ($lockMode !== LockMode::NONE && $lockMode !== LockMode::OPTIMISTIC && $lockMode !== LockMode::PESSIMISTIC_READ && $lockMode !== LockMode::PESSIMISTIC_WRITE) { | ||
throw QueryException::invalidLockMode(); | ||
} | ||
|
||
if ($lockMode === LockMode::PESSIMISTIC_READ) { | ||
$sql .= ' ' . $this->getReadLockSQL($platform); | ||
} elseif ($lockMode === LockMode::PESSIMISTIC_WRITE) { | ||
$sql .= ' ' . $this->getWriteLockSQL($platform); | ||
} | ||
|
||
return $sql; | ||
} | ||
|
||
/** @return FinalizedSelectExecutor */ | ||
public function createExecutor(Query $query): AbstractSqlExecutor | ||
{ | ||
return new FinalizedSelectExecutor($this->finalizeSql($query)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Doctrine\ORM\Query\Exec; | ||
|
||
use Doctrine\ORM\Query; | ||
|
||
/** | ||
* SqlFinalizers are created by OutputWalkers that traversed the DQL AST. | ||
* The SqlFinalizer instance can be kept in the query cache and re-used | ||
* at a later time. | ||
* | ||
* Once the SqlFinalizer has been created or retrieved from the query cache, | ||
* it receives the Query object again in order to yield the AbstractSqlExecutor | ||
* that will then be used to execute the query. | ||
* | ||
* The SqlFinalizer may assume that the DQL that was used to build the AST | ||
* and run the OutputWalker (which created the SqlFinalizer) is equivalent to | ||
* the query that will be passed to the createExecutor() method. Potential differences | ||
* are the parameter values or firstResult/maxResult settings. | ||
*/ | ||
interface SqlFinalizer | ||
{ | ||
public function createExecutor(Query $query): AbstractSqlExecutor; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Doctrine\ORM\Query; | ||
|
||
use Doctrine\ORM\Query\Exec\SqlFinalizer; | ||
|
||
/** | ||
* Interface for output walkers | ||
* | ||
* Output walkers, like tree walkers, can traverse the DQL AST to perform | ||
* their purpose. | ||
* | ||
* The goal of an OutputWalker is to ultimately provide the SqlFinalizer | ||
* which produces the final, executable SQL statement in a "finalization" phase. | ||
* | ||
* It must be possible to use the same SqlFinalizer for Queries with different | ||
* firstResult/maxResult values. In other words, SQL produced by the | ||
* output walker should not depend on those values, and any SQL generation/modification | ||
* specific to them should happen in the finalizer's `\Doctrine\ORM\Query\Exec\SqlFinalizer::createExecutor()` | ||
* method instead. | ||
*/ | ||
interface OutputWalker | ||
{ | ||
/** @param AST\DeleteStatement|AST\UpdateStatement|AST\SelectStatement $AST */ | ||
public function getFinalizer($AST): SqlFinalizer; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.