diff options
Diffstat (limited to 'lib/public/AppFramework/Db/QBMapper.php')
-rw-r--r-- | lib/public/AppFramework/Db/QBMapper.php | 169 |
1 files changed, 99 insertions, 70 deletions
diff --git a/lib/public/AppFramework/Db/QBMapper.php b/lib/public/AppFramework/Db/QBMapper.php index 72373ba26c3..7fb5b2a9afd 100644 --- a/lib/public/AppFramework/Db/QBMapper.php +++ b/lib/public/AppFramework/Db/QBMapper.php @@ -1,37 +1,16 @@ <?php declare(strict_types=1); - /** - * @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl> - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Daniel Kesselberg <mail@danielkesselberg.de> - * @author Joas Schilling <coding@schilljs.com> - * @author Marius David Wieschollek <git.public@mdns.eu> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ - namespace OCP\AppFramework\Db; -use Doctrine\DBAL\Exception\UniqueConstraintViolationException; +use Generator; +use OCP\DB\Exception; use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\DB\Types; use OCP\IDBConnection; /** @@ -43,7 +22,6 @@ use OCP\IDBConnection; * @template T of Entity */ abstract class QBMapper { - /** @var string */ protected $tableName; @@ -56,12 +34,11 @@ abstract class QBMapper { /** * @param IDBConnection $db Instance of the Db abstraction layer * @param string $tableName the name of the table. set this to allow entity - * @param string|null $entityClass the name of the entity that the sql should be - * @psalm-param class-string<T>|null $entityClass the name of the entity that the sql should be - * mapped to queries without using sql + * @param class-string<T>|null $entityClass the name of the entity that the sql should be + * mapped to queries without using sql * @since 14.0.0 */ - public function __construct(IDBConnection $db, string $tableName, string $entityClass = null) { + public function __construct(IDBConnection $db, string $tableName, ?string $entityClass = null) { $this->db = $db; $this->tableName = $tableName; @@ -86,10 +63,12 @@ abstract class QBMapper { /** * Deletes an entity from the table + * * @param Entity $entity the entity that should be deleted * @psalm-param T $entity the entity that should be deleted * @return Entity the deleted entity * @psalm-return T the deleted entity + * @throws Exception * @since 14.0.0 */ public function delete(Entity $entity): Entity { @@ -101,17 +80,19 @@ abstract class QBMapper { ->where( $qb->expr()->eq('id', $qb->createNamedParameter($entity->getId(), $idType)) ); - $qb->execute(); + $qb->executeStatement(); return $entity; } /** * Creates a new entry in the db from an entity + * * @param Entity $entity the entity that should be created * @psalm-param T $entity the entity that should be created * @return Entity the saved entity with the set id * @psalm-return T the saved entity with the set id + * @throws Exception * @since 14.0.0 */ public function insert(Entity $entity): Entity { @@ -132,11 +113,11 @@ abstract class QBMapper { $qb->setValue($column, $qb->createNamedParameter($value, $type)); } - $qb->execute(); + $qb->executeStatement(); if ($entity->id === null) { // When autoincrement is used id is always an int - $entity->setId((int)$qb->getLastInsertId()); + $entity->setId($qb->getLastInsertId()); } return $entity; @@ -151,24 +132,30 @@ abstract class QBMapper { * @psalm-param T $entity the entity that should be created/updated * @return Entity the saved entity with the (new) id * @psalm-return T the saved entity with the (new) id + * @throws Exception * @throws \InvalidArgumentException if entity has no id * @since 15.0.0 */ public function insertOrUpdate(Entity $entity): Entity { try { return $this->insert($entity); - } catch (UniqueConstraintViolationException $ex) { - return $this->update($entity); + } catch (Exception $ex) { + if ($ex->getReason() === Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) { + return $this->update($entity); + } + throw $ex; } } /** * Updates an entry in the db from an entity - * @throws \InvalidArgumentException if entity has no id + * * @param Entity $entity the entity that should be created * @psalm-param T $entity the entity that should be created * @return Entity the saved entity with the set id * @psalm-return T the saved entity with the set id + * @throws Exception + * @throws \InvalidArgumentException if entity has no id * @since 14.0.0 */ public function update(Entity $entity): Entity { @@ -208,7 +195,7 @@ abstract class QBMapper { $qb->where( $qb->expr()->eq('id', $qb->createNamedParameter($id, $idType)) ); - $qb->execute(); + $qb->executeStatement(); return $entity; } @@ -217,13 +204,13 @@ abstract class QBMapper { * Returns the type parameter for the QueryBuilder for a specific property * of the $entity * - * @param Entity $entity The entity to get the types from + * @param Entity $entity The entity to get the types from * @psalm-param T $entity * @param string $property The property of $entity to get the type for - * @return int + * @return int|string * @since 16.0.0 */ - protected function getParameterTypeForProperty(Entity $entity, string $property): int { + protected function getParameterTypeForProperty(Entity $entity, string $property) { $types = $entity->getFieldTypes(); if (!isset($types[ $property ])) { @@ -232,15 +219,34 @@ abstract class QBMapper { switch ($types[ $property ]) { case 'int': - case 'integer': + case Types::INTEGER: + case Types::SMALLINT: return IQueryBuilder::PARAM_INT; - case 'string': + case Types::STRING: return IQueryBuilder::PARAM_STR; case 'bool': - case 'boolean': + case Types::BOOLEAN: return IQueryBuilder::PARAM_BOOL; - case 'blob': + case Types::BLOB: return IQueryBuilder::PARAM_LOB; + case Types::DATE: + return IQueryBuilder::PARAM_DATETIME_MUTABLE; + case Types::DATETIME: + return IQueryBuilder::PARAM_DATETIME_MUTABLE; + case Types::DATETIME_TZ: + return IQueryBuilder::PARAM_DATETIME_TZ_MUTABLE; + case Types::DATE_IMMUTABLE: + return IQueryBuilder::PARAM_DATE_IMMUTABLE; + case Types::DATETIME_IMMUTABLE: + return IQueryBuilder::PARAM_DATETIME_IMMUTABLE; + case Types::DATETIME_TZ_IMMUTABLE: + return IQueryBuilder::PARAM_DATETIME_TZ_IMMUTABLE; + case Types::TIME: + return IQueryBuilder::PARAM_TIME_MUTABLE; + case Types::TIME_IMMUTABLE: + return IQueryBuilder::PARAM_TIME_IMMUTABLE; + case Types::JSON: + return IQueryBuilder::PARAM_JSON; } return IQueryBuilder::PARAM_STR; @@ -250,28 +256,29 @@ abstract class QBMapper { * Returns an db result and throws exceptions when there are more or less * results * - * @see findEntity - * * @param IQueryBuilder $query - * @throws DoesNotExistException if the item does not exist - * @throws MultipleObjectsReturnedException if more than one item exist * @return array the result as row + * @throws Exception + * @throws MultipleObjectsReturnedException if more than one item exist + * @throws DoesNotExistException if the item does not exist + * @see findEntity + * * @since 14.0.0 */ protected function findOneQuery(IQueryBuilder $query): array { - $cursor = $query->execute(); + $result = $query->executeQuery(); - $row = $cursor->fetch(); + $row = $result->fetch(); if ($row === false) { - $cursor->closeCursor(); + $result->closeCursor(); $msg = $this->buildDebugMessage( 'Did expect one result but found none when executing', $query ); throw new DoesNotExistException($msg); } - $row2 = $cursor->fetch(); - $cursor->closeCursor(); + $row2 = $result->fetch(); + $result->closeCursor(); if ($row2 !== false) { $msg = $this->buildDebugMessage( 'Did not expect more than one result when executing', $query @@ -289,8 +296,8 @@ abstract class QBMapper { * @since 14.0.0 */ private function buildDebugMessage(string $msg, IQueryBuilder $sql): string { - return $msg . - ': query "' . $sql->getSQL() . '"; '; + return $msg + . ': query "' . $sql->getSQL() . '"; '; } @@ -304,7 +311,8 @@ abstract class QBMapper { * @since 14.0.0 */ protected function mapRowToEntity(array $row): Entity { - return \call_user_func($this->entityClass .'::fromRow', $row); + unset($row['DOCTRINE_ROWNUM']); // remove doctrine/dbal helper column + return \call_user_func($this->entityClass . '::fromRow', $row); } @@ -312,22 +320,42 @@ abstract class QBMapper { * Runs a sql query and returns an array of entities * * @param IQueryBuilder $query - * @return Entity[] all fetched entities - * @psalm-return T[] all fetched entities + * @return list<Entity> all fetched entities + * @psalm-return list<T> all fetched entities + * @throws Exception * @since 14.0.0 */ protected function findEntities(IQueryBuilder $query): array { - $cursor = $query->execute(); - - $entities = []; - - while ($row = $cursor->fetch()) { - $entities[] = $this->mapRowToEntity($row); + $result = $query->executeQuery(); + try { + $entities = []; + while ($row = $result->fetch()) { + $entities[] = $this->mapRowToEntity($row); + } + return $entities; + } finally { + $result->closeCursor(); } + } - $cursor->closeCursor(); - - return $entities; + /** + * Runs a sql query and yields each resulting entity to obtain database entries in a memory-efficient way + * + * @param IQueryBuilder $query + * @return Generator Generator of fetched entities + * @psalm-return Generator<T> Generator of fetched entities + * @throws Exception + * @since 30.0.0 + */ + protected function yieldEntities(IQueryBuilder $query): Generator { + $result = $query->executeQuery(); + try { + while ($row = $result->fetch()) { + yield $this->mapRowToEntity($row); + } + } finally { + $result->closeCursor(); + } } @@ -336,10 +364,11 @@ abstract class QBMapper { * results * * @param IQueryBuilder $query - * @throws DoesNotExistException if the item does not exist - * @throws MultipleObjectsReturnedException if more than one item exist * @return Entity the entity * @psalm-return T the entity + * @throws Exception + * @throws MultipleObjectsReturnedException if more than one item exist + * @throws DoesNotExistException if the item does not exist * @since 14.0.0 */ protected function findEntity(IQueryBuilder $query): Entity { |