summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorRoeland Jago Douma <rullzer@users.noreply.github.com>2018-05-14 21:12:12 +0200
committerGitHub <noreply@github.com>2018-05-14 21:12:12 +0200
commit497a4facdf0bf1e2ac78967f5e77f1353cf3e8aa (patch)
tree2c947518652ca4583799ac07e726812611b4afde /lib
parent8657f56418e4982eecce3a7a64a1b0db42fc42a7 (diff)
parented7b4839d9ac543799e291ad2d338db559c86354 (diff)
downloadnextcloud-server-497a4facdf0bf1e2ac78967f5e77f1353cf3e8aa.tar.gz
nextcloud-server-497a4facdf0bf1e2ac78967f5e77f1353cf3e8aa.zip
Merge pull request #9444 from nextcloud/techdep/noid/appframework_mapper_to_qb
Add a QueryBuilder based Mapper
Diffstat (limited to 'lib')
-rw-r--r--lib/composer/composer/autoload_classmap.php1
-rw-r--r--lib/composer/composer/autoload_static.php1
-rw-r--r--lib/private/Authentication/Token/DefaultTokenMapper.php3
-rw-r--r--lib/public/AppFramework/Db/Mapper.php15
-rw-r--r--lib/public/AppFramework/Db/QBMapper.php272
5 files changed, 290 insertions, 2 deletions
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php
index 48aa9533e81..cd5090cc93c 100644
--- a/lib/composer/composer/autoload_classmap.php
+++ b/lib/composer/composer/autoload_classmap.php
@@ -23,6 +23,7 @@ return array(
'OCP\\AppFramework\\Db\\Entity' => $baseDir . '/lib/public/AppFramework/Db/Entity.php',
'OCP\\AppFramework\\Db\\Mapper' => $baseDir . '/lib/public/AppFramework/Db/Mapper.php',
'OCP\\AppFramework\\Db\\MultipleObjectsReturnedException' => $baseDir . '/lib/public/AppFramework/Db/MultipleObjectsReturnedException.php',
+ 'OCP\\AppFramework\\Db\\QBMapper' => $baseDir . '/lib/public/AppFramework/Db/QBMapper.php',
'OCP\\AppFramework\\Http' => $baseDir . '/lib/public/AppFramework/Http.php',
'OCP\\AppFramework\\Http\\ContentSecurityPolicy' => $baseDir . '/lib/public/AppFramework/Http/ContentSecurityPolicy.php',
'OCP\\AppFramework\\Http\\DataDisplayResponse' => $baseDir . '/lib/public/AppFramework/Http/DataDisplayResponse.php',
diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php
index 0ed2d0a7632..4e47536eacc 100644
--- a/lib/composer/composer/autoload_static.php
+++ b/lib/composer/composer/autoload_static.php
@@ -53,6 +53,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OCP\\AppFramework\\Db\\Entity' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/Entity.php',
'OCP\\AppFramework\\Db\\Mapper' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/Mapper.php',
'OCP\\AppFramework\\Db\\MultipleObjectsReturnedException' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/MultipleObjectsReturnedException.php',
+ 'OCP\\AppFramework\\Db\\QBMapper' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/QBMapper.php',
'OCP\\AppFramework\\Http' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http.php',
'OCP\\AppFramework\\Http\\ContentSecurityPolicy' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/ContentSecurityPolicy.php',
'OCP\\AppFramework\\Http\\DataDisplayResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/DataDisplayResponse.php',
diff --git a/lib/private/Authentication/Token/DefaultTokenMapper.php b/lib/private/Authentication/Token/DefaultTokenMapper.php
index 41d1b9f203d..55494d72370 100644
--- a/lib/private/Authentication/Token/DefaultTokenMapper.php
+++ b/lib/private/Authentication/Token/DefaultTokenMapper.php
@@ -30,11 +30,12 @@ namespace OC\Authentication\Token;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\Mapper;
+use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use OCP\IUser;
-class DefaultTokenMapper extends Mapper {
+class DefaultTokenMapper extends QBMapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, 'authtoken');
diff --git a/lib/public/AppFramework/Db/Mapper.php b/lib/public/AppFramework/Db/Mapper.php
index b008702ba54..6910757add0 100644
--- a/lib/public/AppFramework/Db/Mapper.php
+++ b/lib/public/AppFramework/Db/Mapper.php
@@ -34,6 +34,7 @@ use OCP\IDBConnection;
* Simple parent class for inheriting your data access layer from. This class
* may be subject to change in the future
* @since 7.0.0
+ * @deprecated 14.0.0 Move over to QBMapper
*/
abstract class Mapper {
@@ -47,6 +48,7 @@ abstract class Mapper {
* @param string $entityClass the name of the entity that the sql should be
* mapped to queries without using sql
* @since 7.0.0
+ * @deprecated 14.0.0 Move over to QBMapper
*/
public function __construct(IDBConnection $db, $tableName, $entityClass=null){
$this->db = $db;
@@ -65,6 +67,7 @@ abstract class Mapper {
/**
* @return string the table name
* @since 7.0.0
+ * @deprecated 14.0.0 Move over to QBMapper
*/
public function getTableName(){
return $this->tableName;
@@ -76,6 +79,7 @@ abstract class Mapper {
* @param Entity $entity the entity that should be deleted
* @return Entity the deleted entity
* @since 7.0.0 - return value added in 8.1.0
+ * @deprecated 14.0.0 Move over to QBMapper
*/
public function delete(Entity $entity){
$sql = 'DELETE FROM `' . $this->tableName . '` WHERE `id` = ?';
@@ -90,6 +94,7 @@ abstract class Mapper {
* @param Entity $entity the entity that should be created
* @return Entity the saved entity with the set id
* @since 7.0.0
+ * @deprecated 14.0.0 Move over to QBMapper
*/
public function insert(Entity $entity){
// get updated fields to save, fields have to be set using a setter to
@@ -139,6 +144,7 @@ abstract class Mapper {
* @param Entity $entity the entity that should be created
* @return Entity the saved entity with the set id
* @since 7.0.0 - return value was added in 8.0.0
+ * @deprecated 14.0.0 Move over to QBMapper
*/
public function update(Entity $entity){
// if entity wasn't changed it makes no sense to run a db query
@@ -195,6 +201,7 @@ abstract class Mapper {
* @param array $array
* @return bool true if associative
* @since 8.1.0
+ * @deprecated 14.0.0 Move over to QBMapper
*/
private function isAssocArray(array $array) {
return array_values($array) !== $array;
@@ -205,6 +212,7 @@ abstract class Mapper {
* @param $value
* @return int PDO constant
* @since 8.1.0
+ * @deprecated 14.0.0 Move over to QBMapper
*/
private function getPDOType($value) {
switch (gettype($value)) {
@@ -226,6 +234,7 @@ abstract class Mapper {
* @param int $offset from which row we want to start
* @return \PDOStatement the database query result
* @since 7.0.0
+ * @deprecated 14.0.0 Move over to QBMapper
*/
protected function execute($sql, array $params=[], $limit=null, $offset=null){
$query = $this->db->prepare($sql, $limit, $offset);
@@ -249,7 +258,6 @@ abstract class Mapper {
return $query;
}
-
/**
* Returns an db result and throws exceptions when there are more or less
* results
@@ -262,6 +270,7 @@ abstract class Mapper {
* @throws MultipleObjectsReturnedException if more than one item exist
* @return array the result as row
* @since 7.0.0
+ * @deprecated 14.0.0 Move over to QBMapper
*/
protected function findOneQuery($sql, array $params=[], $limit=null, $offset=null){
$stmt = $this->execute($sql, $params, $limit, $offset);
@@ -297,6 +306,7 @@ abstract class Mapper {
* @param int $offset from which row we want to start
* @return string formatted error message string
* @since 9.1.0
+ * @deprecated 14.0.0 Move over to QBMapper
*/
private function buildDebugMessage($msg, $sql, array $params=[], $limit=null, $offset=null) {
return $msg .
@@ -313,6 +323,7 @@ abstract class Mapper {
* @param array $row the row which should be converted to an entity
* @return Entity the entity
* @since 7.0.0
+ * @deprecated 14.0.0 Move over to QBMapper
*/
protected function mapRowToEntity($row) {
return call_user_func($this->entityClass .'::fromRow', $row);
@@ -327,6 +338,7 @@ abstract class Mapper {
* @param int $offset from which row we want to start
* @return array all fetched entities
* @since 7.0.0
+ * @deprecated 14.0.0 Move over to QBMapper
*/
protected function findEntities($sql, array $params=[], $limit=null, $offset=null) {
$stmt = $this->execute($sql, $params, $limit, $offset);
@@ -354,6 +366,7 @@ abstract class Mapper {
* @throws MultipleObjectsReturnedException if more than one item exist
* @return Entity the entity
* @since 7.0.0
+ * @deprecated 14.0.0 Move over to QBMapper
*/
protected function findEntity($sql, array $params=[], $limit=null, $offset=null){
return $this->mapRowToEntity($this->findOneQuery($sql, $params, $limit, $offset));
diff --git a/lib/public/AppFramework/Db/QBMapper.php b/lib/public/AppFramework/Db/QBMapper.php
new file mode 100644
index 00000000000..a9b38732a30
--- /dev/null
+++ b/lib/public/AppFramework/Db/QBMapper.php
@@ -0,0 +1,272 @@
+<?php
+declare(strict_types=1);
+/**
+ * @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @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/>.
+ *
+ */
+
+namespace OCP\AppFramework\Db;
+
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IDBConnection;
+
+/**
+ * Simple parent class for inheriting your data access layer from. This class
+ * may be subject to change in the future
+ *
+ * @since 14.0.0
+ */
+abstract class QBMapper {
+
+ /** @var string */
+ protected $tableName;
+
+ /** @var string */
+ protected $entityClass;
+
+ /** @var IDBConnection */
+ protected $db;
+
+ /**
+ * @param IDBConnection $db Instance of the Db abstraction layer
+ * @param string $tableName the name of the table. set this to allow entity
+ * @param string $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){
+ $this->db = $db;
+ $this->tableName = $tableName;
+
+ // if not given set the entity name to the class without the mapper part
+ // cache it here for later use since reflection is slow
+ if($entityClass === null) {
+ $this->entityClass = str_replace('Mapper', '', \get_class($this));
+ } else {
+ $this->entityClass = $entityClass;
+ }
+ }
+
+
+ /**
+ * @return string the table name
+ * @since 14.0.0
+ */
+ public function getTableName(): string {
+ return $this->tableName;
+ }
+
+
+ /**
+ * Deletes an entity from the table
+ * @param Entity $entity the entity that should be deleted
+ * @return Entity the deleted entity
+ * @since 14.0.0
+ */
+ public function delete(Entity $entity): Entity {
+ $qb = $this->db->getQueryBuilder();
+
+ $qb->delete($this->tableName)
+ ->where(
+ $qb->expr()->eq('id', $qb->createNamedParameter($entity->getId()))
+ );
+ $qb->execute();
+ return $entity;
+ }
+
+
+ /**
+ * Creates a new entry in the db from an entity
+ * @param Entity $entity the entity that should be created
+ * @return Entity the saved entity with the set id
+ * @since 14.0.0
+ * @suppress SqlInjectionChecker
+ */
+ public function insert(Entity $entity): Entity {
+ // get updated fields to save, fields have to be set using a setter to
+ // be saved
+ $properties = $entity->getUpdatedFields();
+
+ $qb = $this->db->getQueryBuilder();
+ $qb->insert($this->tableName);
+
+ // build the fields
+ foreach($properties as $property => $updated) {
+ $column = $entity->propertyToColumn($property);
+ $getter = 'get' . ucfirst($property);
+ $value = $entity->$getter();
+
+ $qb->setValue($column, $qb->createNamedParameter($value));
+ }
+
+ $qb->execute();
+
+ $entity->setId((int) $qb->getLastInsertId());
+
+ return $entity;
+ }
+
+
+
+ /**
+ * 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
+ * @return Entity the saved entity with the set id
+ * @since 14.0.0
+ * @suppress SqlInjectionChecker
+ */
+ public function update(Entity $entity): Entity {
+ // if entity wasn't changed it makes no sense to run a db query
+ $properties = $entity->getUpdatedFields();
+ if(\count($properties) === 0) {
+ return $entity;
+ }
+
+ // entity needs an id
+ $id = $entity->getId();
+ if($id === null){
+ throw new \InvalidArgumentException(
+ 'Entity which should be updated has no id');
+ }
+
+ // get updated fields to save, fields have to be set using a setter to
+ // be saved
+ // do not update the id field
+ unset($properties['id']);
+
+ $qb = $this->db->getQueryBuilder();
+ $qb->update($this->tableName);
+
+ // build the fields
+ foreach($properties as $property => $updated) {
+ $column = $entity->propertyToColumn($property);
+ $getter = 'get' . ucfirst($property);
+ $value = $entity->$getter();
+
+ $qb->set($column, $qb->createNamedParameter($value));
+ }
+
+ $qb->where(
+ $qb->expr()->eq('id', $qb->createNamedParameter($id))
+ );
+ $qb->execute();
+
+ return $entity;
+ }
+
+ /**
+ * 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
+ * @since 14.0.0
+ */
+ protected function findOneQuery(IQueryBuilder $query): array {
+ $cursor = $query->execute();
+
+ $row = $cursor->fetch();
+ if($row === false) {
+ $cursor->closeCursor();
+ $msg = $this->buildDebugMessage(
+ 'Did expect one result but found none when executing', $query
+ );
+ throw new DoesNotExistException($msg);
+ }
+
+ $row2 = $cursor->fetch();
+ $cursor->closeCursor();
+ if($row2 !== false ) {
+ $msg = $this->buildDebugMessage(
+ 'Did not expect more than one result when executing', $query
+ );
+ throw new MultipleObjectsReturnedException($msg);
+ }
+
+ return $row;
+ }
+
+ /**
+ * @param string $msg
+ * @param IQueryBuilder $sql
+ * @return string
+ * @since 14.0.0
+ */
+ private function buildDebugMessage(string $msg, IQueryBuilder $sql): string {
+ return $msg .
+ ': query "' . $sql->getSQL() . '"; ';
+ }
+
+
+ /**
+ * Creates an entity from a row. Automatically determines the entity class
+ * from the current mapper name (MyEntityMapper -> MyEntity)
+ *
+ * @param array $row the row which should be converted to an entity
+ * @return Entity the entity
+ * @since 14.0.0
+ */
+ protected function mapRowToEntity(array $row): Entity {
+ return \call_user_func($this->entityClass .'::fromRow', $row);
+ }
+
+
+ /**
+ * Runs a sql query and returns an array of entities
+ *
+ * @param IQueryBuilder $query
+ * @return Entity[] all fetched entities
+ * @since 14.0.0
+ */
+ protected function findEntities(IQueryBuilder $query): array {
+ $cursor = $query->execute();
+
+ $entities = [];
+
+ while($row = $cursor->fetch()){
+ $entities[] = $this->mapRowToEntity($row);
+ }
+
+ $cursor->closeCursor();
+
+ return $entities;
+ }
+
+
+ /**
+ * Returns an db result and throws exceptions when there are more or less
+ * results
+ *
+ * @param IQueryBuilder $query
+ * @throws DoesNotExistException if the item does not exist
+ * @throws MultipleObjectsReturnedException if more than one item exist
+ * @return Entity the entity
+ * @since 14.0.0
+ */
+ protected function findEntity(IQueryBuilder $query): Entity {
+ return $this->mapRowToEntity($this->findOneQuery($query));
+ }
+
+}