From 8e2df99ecd4a5a1712c80902a0f2e85faf043624 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Thu, 22 Jan 2015 17:58:56 +0100 Subject: [PATCH] #1277 DDC-3346 DDC-3531 - moved resultsetmapping into the newly created `CachedPersisterContext` --- .../AbstractEntityInheritancePersister.php | 4 +- .../Entity/BasicEntityPersister.php | 55 ++++---- .../Entity/CachedPersisterContext.php | 121 ++++++++++++++++++ .../Entity/JoinedSubclassPersister.php | 8 +- .../Entity/SingleTablePersister.php | 4 +- 5 files changed, 158 insertions(+), 34 deletions(-) create mode 100644 lib/Doctrine/ORM/Persisters/Entity/CachedPersisterContext.php diff --git a/lib/Doctrine/ORM/Persisters/Entity/AbstractEntityInheritancePersister.php b/lib/Doctrine/ORM/Persisters/Entity/AbstractEntityInheritancePersister.php index edb39afc634..e8f9801cc9d 100644 --- a/lib/Doctrine/ORM/Persisters/Entity/AbstractEntityInheritancePersister.php +++ b/lib/Doctrine/ORM/Persisters/Entity/AbstractEntityInheritancePersister.php @@ -66,7 +66,7 @@ protected function getSelectColumnSQL($field, ClassMetadata $class, $alias = 'r' $sql = $this->getSQLTableAlias($class->name, $tableAlias) . '.' . $this->quoteStrategy->getColumnName($field, $class, $this->platform); - $this->rsm->addFieldResult($alias, $columnAlias, $field, $class->name); + $this->cachedPersisterContexts['noLimits']->rsm->addFieldResult($alias, $columnAlias, $field, $class->name); if (isset($class->fieldMappings[$field]['requireSQLConversion'])) { $type = Type::getType($class->getTypeOfField($field)); @@ -88,7 +88,7 @@ protected function getSelectJoinColumnSQL($tableAlias, $joinColumnName, $classNa { $columnAlias = $this->getSQLColumnAlias($joinColumnName); - $this->rsm->addMetaResult('r', $columnAlias, $joinColumnName, false, $type); + $this->cachedPersisterContexts['noLimits']->rsm->addMetaResult('r', $columnAlias, $joinColumnName, false, $type); return $tableAlias . '.' . $joinColumnName . ' AS ' . $columnAlias; } diff --git a/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php index c53e817c151..30382581181 100644 --- a/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php @@ -134,15 +134,6 @@ class BasicEntityPersister implements EntityPersister */ protected $queuedInserts = array(); - /** - * ResultSetMapping that is used for all queries. Is generated lazily once per request. - * - * TODO: Evaluate Caching in combination with the other cached SQL snippets. - * - * @var Query\ResultSetMapping - */ - protected $rsm; - /** * The map of column names to DBAL mapping types of all prepared columns used * when INSERTing or UPDATEing an entity. @@ -216,6 +207,11 @@ class BasicEntityPersister implements EntityPersister */ private $identifierFlattener; + /** + * @var CachedPersisterContext[] + */ + protected $cachedPersisterContexts = []; + /** * Initializes a new BasicEntityPersister that uses the given EntityManager * and persists instances of the class described by the given ClassMetadata descriptor. @@ -231,6 +227,10 @@ public function __construct(EntityManagerInterface $em, ClassMetadata $class) $this->platform = $this->conn->getDatabasePlatform(); $this->quoteStrategy = $em->getConfiguration()->getQuoteStrategy(); $this->identifierFlattener = new IdentifierFlattener($em->getUnitOfWork(), $em->getMetadataFactory()); + $this->cachedPersisterContexts['noLimits'] = new CachedPersisterContext( + $class, + new Query\ResultSetMapping() + ); } /** @@ -246,7 +246,7 @@ public function getClassMetadata() */ public function getResultSetMapping() { - return $this->rsm; + return $this->cachedPersisterContexts['noLimits']->rsm; } /** @@ -720,7 +720,7 @@ public function load(array $criteria, $entity = null, $assoc = null, array $hint } $hydrator = $this->em->newHydrator($this->selectJoinSql ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT); - $entities = $hydrator->hydrateAll($stmt, $this->rsm, $hints); + $entities = $hydrator->hydrateAll($stmt, $this->cachedPersisterContexts['noLimits']->rsm, $hints); return $entities ? $entities[0] : null; } @@ -809,7 +809,7 @@ public function refresh(array $id, $entity, $lockMode = null) $stmt = $this->conn->executeQuery($sql, $params, $types); $hydrator = $this->em->newHydrator(Query::HYDRATE_OBJECT); - $hydrator->hydrateAll($stmt, $this->rsm, array(Query::HINT_REFRESH => true)); + $hydrator->hydrateAll($stmt, $this->cachedPersisterContexts['noLimits']->rsm, array(Query::HINT_REFRESH => true)); } /** @@ -841,7 +841,7 @@ public function loadCriteria(Criteria $criteria) $stmt = $this->conn->executeQuery($query, $params, $types); $hydrator = $this->em->newHydrator(($this->selectJoinSql) ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT); - return $hydrator->hydrateAll($stmt, $this->rsm, array(UnitOfWork::HINT_DEFEREAGERLOAD => true)); + return $hydrator->hydrateAll($stmt, $this->cachedPersisterContexts['noLimits']->rsm, array(UnitOfWork::HINT_DEFEREAGERLOAD => true)); } /** @@ -886,7 +886,7 @@ public function loadAll(array $criteria = array(), array $orderBy = null, $limit $hydrator = $this->em->newHydrator(($this->selectJoinSql) ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT); - return $hydrator->hydrateAll($stmt, $this->rsm, array(UnitOfWork::HINT_DEFEREAGERLOAD => true)); + return $hydrator->hydrateAll($stmt, $this->cachedPersisterContexts['noLimits']->rsm, array(UnitOfWork::HINT_DEFEREAGERLOAD => true)); } /** @@ -909,11 +909,11 @@ public function getManyToManyCollection(array $assoc, $sourceEntity, $offset = n */ private function loadArrayFromStatement($assoc, $stmt) { - $rsm = $this->rsm; + $rsm = $this->cachedPersisterContexts['noLimits']->rsm; $hints = array(UnitOfWork::HINT_DEFEREAGERLOAD => true); if (isset($assoc['indexBy'])) { - $rsm = clone ($this->rsm); // this is necessary because the "default rsm" should be changed. + $rsm = clone ($this->cachedPersisterContexts['noLimits']->rsm); // this is necessary because the "default rsm" should be changed. $rsm->addIndexBy('r', $assoc['indexBy']); } @@ -931,14 +931,14 @@ private function loadArrayFromStatement($assoc, $stmt) */ private function loadCollectionFromStatement($assoc, $stmt, $coll) { - $rsm = $this->rsm; + $rsm = $this->cachedPersisterContexts['noLimits']->rsm; $hints = array( UnitOfWork::HINT_DEFEREAGERLOAD => true, 'collection' => $coll ); if (isset($assoc['indexBy'])) { - $rsm = clone ($this->rsm); // this is necessary because the "default rsm" should be changed. + $rsm = clone ($this->cachedPersisterContexts['noLimits']->rsm); // this is necessary because the "default rsm" should be changed. $rsm->addIndexBy('r', $assoc['indexBy']); } @@ -1062,7 +1062,7 @@ public function getSelectSQL($criteria, $assoc = null, $lockMode = null, $limit break; } - $columnList = $this->getSelectColumnsSQL(); + $columnList = $this->getSelectColumnsSQL(null !== $limit); $tableAlias = $this->getSQLTableAlias($this->class->name); $filterSql = $this->generateFilterConditionSQL($this->class, $tableAlias); $tableName = $this->quoteStrategy->getTableName($this->class, $this->platform); @@ -1180,17 +1180,19 @@ protected final function getOrderBySQL(array $orderBy, $baseTableAlias) * the resulting SQL fragment is generated only once and cached in {@link selectColumnListSql}. * Subclasses may or may not do the same. * + * @param bool $hasLimitClause + * * @return string The SQL fragment. */ - protected function getSelectColumnsSQL() + protected function getSelectColumnsSQL(/*$hasLimitClause = false*/) { + //if ( ! $hasLimitClause && $this->selectColumnListSql !== null) { if ($this->selectColumnListSql !== null) { return $this->selectColumnListSql; } $columnList = array(); - $this->rsm = new Query\ResultSetMapping(); - $this->rsm->addEntityResult($this->class->name, 'r'); // r for root + $this->cachedPersisterContexts['noLimits']->rsm->addEntityResult($this->class->name, 'r'); // r for root // Add regular columns to select list foreach ($this->class->fieldNames as $field) { @@ -1210,6 +1212,7 @@ protected function getSelectColumnsSQL() $isAssocToOneInverseSide = $assoc['type'] & ClassMetadata::TO_ONE && ! $assoc['isOwningSide']; $isAssocFromOneEager = $assoc['type'] !== ClassMetadata::MANY_TO_MANY && $assoc['fetch'] === ClassMetadata::FETCH_EAGER; + //if ($hasLimitClause || ! ($isAssocFromOneEager || $isAssocToOneInverseSide)) { if ( ! ($isAssocFromOneEager || $isAssocToOneInverseSide)) { continue; } @@ -1221,7 +1224,7 @@ protected function getSelectColumnsSQL() } $assocAlias = 'e' . ($eagerAliasCounter++); - $this->rsm->addJoinedEntityResult($assoc['targetEntity'], $assocAlias, 'r', $assocField); + $this->cachedPersisterContexts['noLimits']->rsm->addJoinedEntityResult($assoc['targetEntity'], $assocAlias, 'r', $assocField); foreach ($eagerEntity->fieldNames as $field) { $columnList[] = $this->getSelectColumnSQL($field, $eagerEntity, $assocAlias); @@ -1241,7 +1244,7 @@ protected function getSelectColumnsSQL() $joinCondition = array(); if (isset($assoc['indexBy'])) { - $this->rsm->addIndexBy($assocAlias, $assoc['indexBy']); + $this->cachedPersisterContexts['noLimits']->rsm->addIndexBy($assocAlias, $assoc['indexBy']); } if ( ! $assoc['isOwningSide']) { @@ -1318,7 +1321,7 @@ protected function getSelectColumnAssociationSQL($field, $assoc, ClassMetadata $ . '.' . $quotedColumn . ' AS ' . $resultColumnName; $type = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em); - $this->rsm->addMetaResult($alias, $resultColumnName, $quotedColumn, $isIdentifier, $type); + $this->cachedPersisterContexts['noLimits']->rsm->addMetaResult($alias, $resultColumnName, $quotedColumn, $isIdentifier, $type); } return implode(', ', $columnList); @@ -1456,7 +1459,7 @@ protected function getSelectColumnSQL($field, ClassMetadata $class, $alias = 'r' $sql = $tableAlias . '.' . $columnName; $columnAlias = $this->getSQLColumnAlias($class->columnNames[$field]); - $this->rsm->addFieldResult($alias, $columnAlias, $field); + $this->cachedPersisterContexts['noLimits']->rsm->addFieldResult($alias, $columnAlias, $field); if (isset($class->fieldMappings[$field]['requireSQLConversion'])) { $type = Type::getType($class->getTypeOfField($field)); diff --git a/lib/Doctrine/ORM/Persisters/Entity/CachedPersisterContext.php b/lib/Doctrine/ORM/Persisters/Entity/CachedPersisterContext.php new file mode 100644 index 00000000000..a148a54dda7 --- /dev/null +++ b/lib/Doctrine/ORM/Persisters/Entity/CachedPersisterContext.php @@ -0,0 +1,121 @@ +. + */ + +namespace Doctrine\ORM\Persisters\Entity; +use Doctrine\Common\Persistence\Mapping\ClassMetadata; +use Doctrine\ORM\Query\ResultSetMapping; + +/** + * A swappable persister context to use as a container for the current + * generated query/resultSetMapping/type binding information. + * + * This class is a utility class to be used only by the persister API + * + * This object is highly mutable due to performance reasons. Same reasoning + * behind its properties being public. + * + * @author Marco Pivetta + */ +class CachedPersisterContext +{ + /** + * Metadata object that describes the mapping of the mapped entity class. + * + * @var \Doctrine\ORM\Mapping\ClassMetadata + */ + public $class; + + /** + * ResultSetMapping that is used for all queries. Is generated lazily once per request. + * + * @var \Doctrine\ORM\Query\ResultSetMapping + */ + public $rsm; + + /** + * The map of column names to DBAL mapping types of all prepared columns used + * when INSERTing or UPDATEing an entity. + * + * @var array + * + * @see \Doctrine\ORM\Persisters\Entity\BasicEntityPersister#prepareInsertData($entity) + * @see \Doctrine\ORM\Persisters\Entity\BasicEntityPersister#prepareUpdateData($entity) + */ + public $columnTypes = array(); + + /** + * The map of quoted column names. + * + * @var array + * + * @see \Doctrine\ORM\Persisters\Entity\BasicEntityPersister#prepareInsertData($entity) + * @see \Doctrine\ORM\Persisters\Entity\BasicEntityPersister#prepareUpdateData($entity) + */ + public $quotedColumns = array(); + + /** + * The INSERT SQL statement used for entities handled by this persister. + * This SQL is only generated once per request, if at all. + * + * @var string + */ + public $insertSql = ''; + + /** + * The SELECT column list SQL fragment used for querying entities by this persister. + * This SQL fragment is only generated once per request, if at all. + * + * @var string + */ + public $selectColumnListSql; + + /** + * The JOIN SQL fragment used to eagerly load all many-to-one and one-to-one + * associations configured as FETCH_EAGER, as well as all inverse one-to-one associations. + * + * @var string + */ + public $selectJoinSql; + + /** + * Counter for creating unique SQL table and column aliases. + * + * @var integer + */ + public $sqlAliasCounter = 0; + + /** + * Map from class names (FQCN) to the corresponding generated SQL table aliases. + * + * @var array + */ + public $sqlTableAliases = array(); + + /** + * @param ClassMetadata $class + * @param ResultSetMapping $rsm + */ + public function __construct( + ClassMetadata $class, + ResultSetMapping $rsm + ) { + $this->class = $class; + $this->rsm = $rsm; + } +} diff --git a/lib/Doctrine/ORM/Persisters/Entity/JoinedSubclassPersister.php b/lib/Doctrine/ORM/Persisters/Entity/JoinedSubclassPersister.php index 019d987d5fb..3ec9afc309f 100644 --- a/lib/Doctrine/ORM/Persisters/Entity/JoinedSubclassPersister.php +++ b/lib/Doctrine/ORM/Persisters/Entity/JoinedSubclassPersister.php @@ -425,14 +425,14 @@ protected function getSelectColumnsSQL() } $columnList = array(); - $this->rsm = new ResultSetMapping(); + //$this->cachedPersisterContexts['noLimits']->rsm = new ResultSetMapping(); $discrColumn = $this->class->discriminatorColumn['name']; $baseTableAlias = $this->getSQLTableAlias($this->class->name); $resultColumnName = $this->platform->getSQLResultCasing($discrColumn); - $this->rsm->addEntityResult($this->class->name, 'r'); - $this->rsm->setDiscriminatorColumn('r', $resultColumnName); - $this->rsm->addMetaResult('r', $resultColumnName, $discrColumn); + $this->cachedPersisterContexts['noLimits']->rsm->addEntityResult($this->class->name, 'r'); + $this->cachedPersisterContexts['noLimits']->rsm->setDiscriminatorColumn('r', $resultColumnName); + $this->cachedPersisterContexts['noLimits']->rsm->addMetaResult('r', $resultColumnName, $discrColumn); // Add regular columns foreach ($this->class->fieldMappings as $fieldName => $mapping) { diff --git a/lib/Doctrine/ORM/Persisters/Entity/SingleTablePersister.php b/lib/Doctrine/ORM/Persisters/Entity/SingleTablePersister.php index 23ebdf2d2d9..b93c2afc9e5 100644 --- a/lib/Doctrine/ORM/Persisters/Entity/SingleTablePersister.php +++ b/lib/Doctrine/ORM/Persisters/Entity/SingleTablePersister.php @@ -63,8 +63,8 @@ protected function getSelectColumnsSQL() $resultColumnName = $this->platform->getSQLResultCasing($discrColumn); - $this->rsm->setDiscriminatorColumn('r', $resultColumnName); - $this->rsm->addMetaResult('r', $resultColumnName, $discrColumn); + $this->cachedPersisterContexts['noLimits']->rsm->setDiscriminatorColumn('r', $resultColumnName); + $this->cachedPersisterContexts['noLimits']->rsm->addMetaResult('r', $resultColumnName, $discrColumn); // Append subclass columns foreach ($this->class->subClasses as $subClassName) {