aboutsummaryrefslogtreecommitdiffstats
path: root/lib/public/AppFramework/Db/QBMapper.php
diff options
context:
space:
mode:
Diffstat (limited to 'lib/public/AppFramework/Db/QBMapper.php')
-rw-r--r--lib/public/AppFramework/Db/QBMapper.php169
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 {