summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorChristoph Wurst <ChristophWurst@users.noreply.github.com>2021-01-14 12:27:23 +0100
committerGitHub <noreply@github.com>2021-01-14 12:27:23 +0100
commitb9287f9780c6743f0f5e2db84fe32c470cf70b22 (patch)
treec34823626526580de9a9f3b0e1b77e28600b042f /lib
parent5a141bd1622c3cb3573740e81ce573453121800c (diff)
parent2c9cdc1cdbf900a8578c80075f5ea2da479c4f80 (diff)
downloadnextcloud-server-b9287f9780c6743f0f5e2db84fe32c470cf70b22.tar.gz
nextcloud-server-b9287f9780c6743f0f5e2db84fe32c470cf70b22.zip
Merge pull request #25091 from nextcloud/enhancement/ocp-db-exception-abstraction
Add our own DB exception abstraction
Diffstat (limited to 'lib')
-rw-r--r--lib/composer/composer/autoload_classmap.php2
-rw-r--r--lib/composer/composer/autoload_static.php2
-rw-r--r--lib/private/DB/Adapter.php10
-rw-r--r--lib/private/DB/Connection.php25
-rw-r--r--lib/private/DB/ConnectionAdapter.php118
-rw-r--r--lib/private/DB/Exceptions/DbalException.php136
-rw-r--r--lib/private/DB/Migrator.php7
-rw-r--r--lib/public/DB/Exception.php149
-rw-r--r--lib/public/DB/QueryBuilder/IQueryBuilder.php2
-rw-r--r--lib/public/IDBConnection.php15
10 files changed, 439 insertions, 27 deletions
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php
index dc5f9dc9e8e..b1068d1a4b2 100644
--- a/lib/composer/composer/autoload_classmap.php
+++ b/lib/composer/composer/autoload_classmap.php
@@ -161,6 +161,7 @@ return array(
'OCP\\Contacts\\ContactsMenu\\IProvider' => $baseDir . '/lib/public/Contacts/ContactsMenu/IProvider.php',
'OCP\\Contacts\\Events\\ContactInteractedWithEvent' => $baseDir . '/lib/public/Contacts/Events/ContactInteractedWithEvent.php',
'OCP\\Contacts\\IManager' => $baseDir . '/lib/public/Contacts/IManager.php',
+ 'OCP\\DB\\Exception' => $baseDir . '/lib/public/DB/Exception.php',
'OCP\\DB\\IPreparedStatement' => $baseDir . '/lib/public/DB/IPreparedStatement.php',
'OCP\\DB\\IResult' => $baseDir . '/lib/public/DB/IResult.php',
'OCP\\DB\\ISchemaWrapper' => $baseDir . '/lib/public/DB/ISchemaWrapper.php',
@@ -953,6 +954,7 @@ return array(
'OC\\DB\\Connection' => $baseDir . '/lib/private/DB/Connection.php',
'OC\\DB\\ConnectionAdapter' => $baseDir . '/lib/private/DB/ConnectionAdapter.php',
'OC\\DB\\ConnectionFactory' => $baseDir . '/lib/private/DB/ConnectionFactory.php',
+ 'OC\\DB\\Exceptions\\DbalException' => $baseDir . '/lib/private/DB/Exceptions/DbalException.php',
'OC\\DB\\MDB2SchemaManager' => $baseDir . '/lib/private/DB/MDB2SchemaManager.php',
'OC\\DB\\MDB2SchemaReader' => $baseDir . '/lib/private/DB/MDB2SchemaReader.php',
'OC\\DB\\MigrationException' => $baseDir . '/lib/private/DB/MigrationException.php',
diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php
index 455fb01b18e..d1e11bbb09a 100644
--- a/lib/composer/composer/autoload_static.php
+++ b/lib/composer/composer/autoload_static.php
@@ -190,6 +190,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OCP\\Contacts\\ContactsMenu\\IProvider' => __DIR__ . '/../../..' . '/lib/public/Contacts/ContactsMenu/IProvider.php',
'OCP\\Contacts\\Events\\ContactInteractedWithEvent' => __DIR__ . '/../../..' . '/lib/public/Contacts/Events/ContactInteractedWithEvent.php',
'OCP\\Contacts\\IManager' => __DIR__ . '/../../..' . '/lib/public/Contacts/IManager.php',
+ 'OCP\\DB\\Exception' => __DIR__ . '/../../..' . '/lib/public/DB/Exception.php',
'OCP\\DB\\IPreparedStatement' => __DIR__ . '/../../..' . '/lib/public/DB/IPreparedStatement.php',
'OCP\\DB\\IResult' => __DIR__ . '/../../..' . '/lib/public/DB/IResult.php',
'OCP\\DB\\ISchemaWrapper' => __DIR__ . '/../../..' . '/lib/public/DB/ISchemaWrapper.php',
@@ -982,6 +983,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\DB\\Connection' => __DIR__ . '/../../..' . '/lib/private/DB/Connection.php',
'OC\\DB\\ConnectionAdapter' => __DIR__ . '/../../..' . '/lib/private/DB/ConnectionAdapter.php',
'OC\\DB\\ConnectionFactory' => __DIR__ . '/../../..' . '/lib/private/DB/ConnectionFactory.php',
+ 'OC\\DB\\Exceptions\\DbalException' => __DIR__ . '/../../..' . '/lib/private/DB/Exceptions/DbalException.php',
'OC\\DB\\MDB2SchemaManager' => __DIR__ . '/../../..' . '/lib/private/DB/MDB2SchemaManager.php',
'OC\\DB\\MDB2SchemaReader' => __DIR__ . '/../../..' . '/lib/private/DB/MDB2SchemaReader.php',
'OC\\DB\\MigrationException' => __DIR__ . '/../../..' . '/lib/private/DB/MigrationException.php',
diff --git a/lib/private/DB/Adapter.php b/lib/private/DB/Adapter.php
index 49b831301be..62514e7d83a 100644
--- a/lib/private/DB/Adapter.php
+++ b/lib/private/DB/Adapter.php
@@ -30,6 +30,7 @@
namespace OC\DB;
+use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
/**
@@ -49,7 +50,9 @@ class Adapter {
/**
* @param string $table name
+ *
* @return int id of last insert statement
+ * @throws Exception
*/
public function lastInsertId($table) {
return (int) $this->conn->realLastInsertId($table);
@@ -67,6 +70,7 @@ class Adapter {
* Create an exclusive read+write lock on a table
*
* @param string $tableName
+ * @throws Exception
* @since 9.1.0
*/
public function lockTable($tableName) {
@@ -77,6 +81,7 @@ class Adapter {
/**
* Release a previous acquired lock again
*
+ * @throws Exception
* @since 9.1.0
*/
public function unlockTable() {
@@ -94,7 +99,7 @@ class Adapter {
* If this is null or an empty array, all keys of $input will be compared
* Please note: text fields (clob) must not be used in the compare array
* @return int number of inserted rows
- * @throws \Doctrine\DBAL\Exception
+ * @throws Exception
* @deprecated 15.0.0 - use unique index and "try { $db->insert() } catch (UniqueConstraintViolationException $e) {}" instead, because it is more reliable and does not have the risk for deadlocks - see https://github.com/nextcloud/server/pull/12371
*/
public function insertIfNotExist($table, $input, array $compare = null) {
@@ -130,6 +135,9 @@ class Adapter {
}
}
+ /**
+ * @throws \OCP\DB\Exception
+ */
public function insertIgnoreConflict(string $table,array $values) : int {
try {
$builder = $this->conn->getQueryBuilder();
diff --git a/lib/private/DB/Connection.php b/lib/private/DB/Connection.php
index c67c6df0826..cb7af4d51e2 100644
--- a/lib/private/DB/Connection.php
+++ b/lib/private/DB/Connection.php
@@ -74,6 +74,9 @@ class Connection extends ReconnectWrapper {
/** @var int */
protected $queriesExecuted = 0;
+ /**
+ * @throws Exception
+ */
public function connect() {
try {
return parent::connect();
@@ -183,7 +186,9 @@ class Connection extends ReconnectWrapper {
* @param string $statement The SQL statement to prepare.
* @param int $limit
* @param int $offset
+ *
* @return Statement The prepared statement.
+ * @throws Exception
*/
public function prepare($statement, $limit = null, $offset = null): Statement {
if ($limit === -1) {
@@ -221,6 +226,9 @@ class Connection extends ReconnectWrapper {
return parent::executeQuery($sql, $params, $types, $qcp);
}
+ /**
+ * @throws Exception
+ */
public function executeUpdate(string $sql, array $params = [], array $types = []): int {
$sql = $this->replaceTablePrefix($sql);
$sql = $this->adapter->fixupStatement($sql);
@@ -258,7 +266,9 @@ class Connection extends ReconnectWrapper {
* columns or sequences.
*
* @param string $seqName Name of the sequence object from which the ID should be returned.
+ *
* @return string the last inserted ID.
+ * @throws Exception
*/
public function lastInsertId($seqName = null) {
if ($seqName) {
@@ -267,7 +277,10 @@ class Connection extends ReconnectWrapper {
return $this->adapter->lastInsertId($seqName);
}
- // internal use
+ /**
+ * @internal
+ * @throws Exception
+ */
public function realLastInsertId($seqName = null) {
return parent::lastInsertId($seqName);
}
@@ -364,7 +377,9 @@ class Connection extends ReconnectWrapper {
* Create an exclusive read+write lock on a table
*
* @param string $tableName
+ *
* @throws \BadMethodCallException When trying to acquire a second lock
+ * @throws Exception
* @since 9.1.0
*/
public function lockTable($tableName) {
@@ -380,6 +395,7 @@ class Connection extends ReconnectWrapper {
/**
* Release a previous acquired lock again
*
+ * @throws Exception
* @since 9.1.0
*/
public function unlockTable() {
@@ -415,6 +431,8 @@ class Connection extends ReconnectWrapper {
* Drop a table from the database if it exists
*
* @param string $table table name without the prefix
+ *
+ * @throws Exception
*/
public function dropTable($table) {
$table = $this->tablePrefix . trim($table);
@@ -428,7 +446,9 @@ class Connection extends ReconnectWrapper {
* Check if a table exists
*
* @param string $table table name without the prefix
+ *
* @return bool
+ * @throws Exception
*/
public function tableExists($table) {
$table = $this->tablePrefix . trim($table);
@@ -483,6 +503,7 @@ class Connection extends ReconnectWrapper {
* Create the schema of the connected database
*
* @return Schema
+ * @throws Exception
*/
public function createSchema() {
$schemaManager = new MDB2SchemaManager($this);
@@ -494,6 +515,8 @@ class Connection extends ReconnectWrapper {
* Migrate the database to the given schema
*
* @param Schema $toSchema
+ *
+ * @throws Exception
*/
public function migrateToSchema(Schema $toSchema) {
$schemaManager = new MDB2SchemaManager($this);
diff --git a/lib/private/DB/ConnectionAdapter.php b/lib/private/DB/ConnectionAdapter.php
index 97a0b60044d..62c013e4dcf 100644
--- a/lib/private/DB/ConnectionAdapter.php
+++ b/lib/private/DB/ConnectionAdapter.php
@@ -25,8 +25,10 @@ declare(strict_types=1);
namespace OC\DB;
+use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Schema\Schema;
+use OC\DB\Exceptions\DbalException;
use OCP\DB\IPreparedStatement;
use OCP\DB\IResult;
use OCP\DB\QueryBuilder\IQueryBuilder;
@@ -49,51 +51,95 @@ class ConnectionAdapter implements IDBConnection {
}
public function prepare($sql, $limit = null, $offset = null): IPreparedStatement {
- return new PreparedStatement(
- $this->inner->prepare($sql, $limit, $offset)
- );
+ try {
+ return new PreparedStatement(
+ $this->inner->prepare($sql, $limit, $offset)
+ );
+ } catch (Exception $e) {
+ throw DbalException::wrap($e);
+ }
}
public function executeQuery(string $sql, array $params = [], $types = []): IResult {
- return new ResultAdapter(
- $this->inner->executeQuery($sql, $params, $types)
- );
+ try {
+ return new ResultAdapter(
+ $this->inner->executeQuery($sql, $params, $types)
+ );
+ } catch (Exception $e) {
+ throw DbalException::wrap($e);
+ }
}
public function executeUpdate(string $sql, array $params = [], array $types = []): int {
- return $this->inner->executeUpdate($sql, $params, $types);
+ try {
+ return $this->inner->executeUpdate($sql, $params, $types);
+ } catch (Exception $e) {
+ throw DbalException::wrap($e);
+ }
}
public function executeStatement($sql, array $params = [], array $types = []): int {
- return $this->inner->executeStatement($sql, $params, $types);
+ try {
+ return $this->inner->executeStatement($sql, $params, $types);
+ } catch (Exception $e) {
+ throw DbalException::wrap($e);
+ }
}
public function lastInsertId(string $table): int {
- return (int) $this->inner->lastInsertId($table);
+ try {
+ return (int)$this->inner->lastInsertId($table);
+ } catch (Exception $e) {
+ throw DbalException::wrap($e);
+ }
}
public function insertIfNotExist(string $table, array $input, array $compare = null) {
- return $this->inner->insertIfNotExist($table, $input, $compare);
+ try {
+ return $this->inner->insertIfNotExist($table, $input, $compare);
+ } catch (Exception $e) {
+ throw DbalException::wrap($e);
+ }
}
public function insertIgnoreConflict(string $table, array $values): int {
- return $this->inner->insertIgnoreConflict($table, $values);
+ try {
+ return $this->inner->insertIgnoreConflict($table, $values);
+ } catch (Exception $e) {
+ throw DbalException::wrap($e);
+ }
}
public function setValues($table, array $keys, array $values, array $updatePreconditionValues = []): int {
- return $this->inner->setValues($table, $keys, $values, $updatePreconditionValues);
+ try {
+ return $this->inner->setValues($table, $keys, $values, $updatePreconditionValues);
+ } catch (Exception $e) {
+ throw DbalException::wrap($e);
+ }
}
public function lockTable($tableName): void {
- $this->inner->lockTable($tableName);
+ try {
+ $this->inner->lockTable($tableName);
+ } catch (Exception $e) {
+ throw DbalException::wrap($e);
+ }
}
public function unlockTable(): void {
- $this->inner->unlockTable();
+ try {
+ $this->inner->unlockTable();
+ } catch (Exception $e) {
+ throw DbalException::wrap($e);
+ }
}
public function beginTransaction(): void {
- $this->inner->beginTransaction();
+ try {
+ $this->inner->beginTransaction();
+ } catch (Exception $e) {
+ throw DbalException::wrap($e);
+ }
}
public function inTransaction(): bool {
@@ -101,11 +147,19 @@ class ConnectionAdapter implements IDBConnection {
}
public function commit(): void {
- $this->inner->commit();
+ try {
+ $this->inner->commit();
+ } catch (Exception $e) {
+ throw DbalException::wrap($e);
+ }
}
public function rollBack(): void {
- $this->inner->rollBack();
+ try {
+ $this->inner->rollBack();
+ } catch (Exception $e) {
+ throw DbalException::wrap($e);
+ }
}
public function getError(): string {
@@ -121,7 +175,11 @@ class ConnectionAdapter implements IDBConnection {
}
public function connect(): bool {
- return $this->inner->connect();
+ try {
+ return $this->inner->connect();
+ } catch (Exception $e) {
+ throw DbalException::wrap($e);
+ }
}
public function close(): void {
@@ -140,11 +198,19 @@ class ConnectionAdapter implements IDBConnection {
}
public function dropTable(string $table): void {
- $this->inner->dropTable($table);
+ try {
+ $this->inner->dropTable($table);
+ } catch (Exception $e) {
+ throw DbalException::wrap($e);
+ }
}
public function tableExists(string $table): bool {
- return $this->inner->tableExists($table);
+ try {
+ return $this->inner->tableExists($table);
+ } catch (Exception $e) {
+ throw DbalException::wrap($e);
+ }
}
public function escapeLikeParameter(string $param): string {
@@ -159,11 +225,19 @@ class ConnectionAdapter implements IDBConnection {
* @todo leaks a 3rdparty type
*/
public function createSchema(): Schema {
- return $this->inner->createSchema();
+ try {
+ return $this->inner->createSchema();
+ } catch (Exception $e) {
+ throw DbalException::wrap($e);
+ }
}
public function migrateToSchema(Schema $toSchema): void {
- $this->inner->migrateToSchema($toSchema);
+ try {
+ $this->inner->migrateToSchema($toSchema);
+ } catch (Exception $e) {
+ throw DbalException::wrap($e);
+ }
}
public function getInner(): Connection {
diff --git a/lib/private/DB/Exceptions/DbalException.php b/lib/private/DB/Exceptions/DbalException.php
new file mode 100644
index 00000000000..4e0e1517048
--- /dev/null
+++ b/lib/private/DB/Exceptions/DbalException.php
@@ -0,0 +1,136 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * @copyright 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @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 OC\DB\Exceptions;
+
+use Doctrine\DBAL\ConnectionException;
+use Doctrine\DBAL\Exception\ConstraintViolationException;
+use Doctrine\DBAL\Exception\DatabaseObjectExistsException;
+use Doctrine\DBAL\Exception\DatabaseObjectNotFoundException;
+use Doctrine\DBAL\Exception\DeadlockException;
+use Doctrine\DBAL\Exception\DriverException;
+use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
+use Doctrine\DBAL\Exception\InvalidArgumentException;
+use Doctrine\DBAL\Exception\InvalidFieldNameException;
+use Doctrine\DBAL\Exception\NonUniqueFieldNameException;
+use Doctrine\DBAL\Exception\NotNullConstraintViolationException;
+use Doctrine\DBAL\Exception\ServerException;
+use Doctrine\DBAL\Exception\SyntaxErrorException;
+use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
+use OCP\DB\Exception;
+
+/**
+ * Wrapper around the raw dbal exception, so we can pass it to apps that catch
+ * our OCP db exception
+ *
+ * @psalm-immutable
+ */
+class DbalException extends Exception {
+
+ /** @var \Doctrine\DBAL\Exception */
+ private $original;
+
+ /**
+ * @param \Doctrine\DBAL\Exception $original
+ * @param int $code
+ * @param string $message
+ */
+ private function __construct(\Doctrine\DBAL\Exception $original, int $code, string $message) {
+ parent::__construct(
+ $message,
+ $code,
+ $original
+ );
+ $this->original = $original;
+ }
+
+ public static function wrap(\Doctrine\DBAL\Exception $original, string $message = ''): self {
+ return new self(
+ $original,
+ is_int($original->getCode()) ? $original->getCode() : 0,
+ empty($message) ? $original->getMessage() : $message
+ );
+ }
+
+ public function getReason(): ?int {
+ /**
+ * Generic errors
+ */
+ if ($this->original instanceof ConnectionException) {
+ return parent::REASON_CONNECTION_LOST;
+ }
+ if ($this->original instanceof DriverException) {
+ return parent::REASON_DRIVER;
+ }
+ if ($this->original instanceof InvalidArgumentException) {
+ return parent::REASON_INVALID_ARGUMENT;
+ }
+
+ /**
+ * Constraint errors
+ */
+ if ($this->original instanceof ForeignKeyConstraintViolationException) {
+ return parent::REASON_FOREIGN_KEY_VIOLATION;
+ }
+ if ($this->original instanceof NotNullConstraintViolationException) {
+ return parent::REASON_NOT_NULL_CONSTRAINT_VIOLATION;
+ }
+ if ($this->original instanceof UniqueConstraintViolationException) {
+ return parent::REASON_UNIQUE_CONSTRAINT_VIOLATION;
+ }
+ // The base exception comes last
+ if ($this->original instanceof ConstraintViolationException) {
+ return parent::REASON_CONSTRAINT_VIOLATION;
+ }
+
+ /**
+ * Other server errors
+ */
+ if ($this->original instanceof DatabaseObjectExistsException) {
+ return parent::REASON_DATABASE_OBJECT_EXISTS;
+ }
+ if ($this->original instanceof DatabaseObjectNotFoundException) {
+ return parent::REASON_DATABASE_OBJECT_NOT_FOUND;
+ }
+ if ($this->original instanceof DeadlockException) {
+ return parent::REASON_DEADLOCK;
+ }
+ if ($this->original instanceof InvalidFieldNameException) {
+ return parent::REASON_INVALID_FIELD_NAME;
+ }
+ if ($this->original instanceof NonUniqueFieldNameException) {
+ return parent::REASON_NON_UNIQUE_FIELD_NAME;
+ }
+ if ($this->original instanceof SyntaxErrorException) {
+ return parent::REASON_SYNTAX_ERROR;
+ }
+ // The base server exception class comes last
+ if ($this->original instanceof ServerException) {
+ return parent::REASON_SERVER;
+ }
+
+ return null;
+ }
+}
diff --git a/lib/private/DB/Migrator.php b/lib/private/DB/Migrator.php
index e50927f620b..dcf0db89f72 100644
--- a/lib/private/DB/Migrator.php
+++ b/lib/private/DB/Migrator.php
@@ -82,6 +82,8 @@ class Migrator {
/**
* @param \Doctrine\DBAL\Schema\Schema $targetSchema
+ *
+ * @throws Exception
*/
public function migrate(Schema $targetSchema) {
$this->noEmit = true;
@@ -171,6 +173,9 @@ class Migrator {
return new Table($newName, $table->getColumns(), $newIndexes, [], [], $table->getOptions());
}
+ /**
+ * @throws Exception
+ */
public function createSchema() {
$this->connection->getConfiguration()->setSchemaAssetsFilter(function ($asset) {
/** @var string|AbstractAsset $asset */
@@ -231,6 +236,8 @@ class Migrator {
/**
* @param \Doctrine\DBAL\Schema\Schema $targetSchema
* @param \Doctrine\DBAL\Connection $connection
+ *
+ * @throws Exception
*/
protected function applySchema(Schema $targetSchema, \Doctrine\DBAL\Connection $connection = null) {
if (is_null($connection)) {
diff --git a/lib/public/DB/Exception.php b/lib/public/DB/Exception.php
new file mode 100644
index 00000000000..1154530e85d
--- /dev/null
+++ b/lib/public/DB/Exception.php
@@ -0,0 +1,149 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * @copyright 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @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\DB;
+
+use Exception as BaseException;
+
+/**
+ * Database exception
+ *
+ * Thrown by Nextcloud's database abstraction layer. This is the base class that
+ * any specific exception will extend. Use this class in your try-catch to catch
+ * *any* error related to the database. Use any of the subclasses in the same
+ * namespace if you are only interested in specific errors.
+ *
+ * @psalm-immutable
+ * @since 21.0.0
+ */
+class Exception extends BaseException {
+
+ /**
+ * Nextcloud lost connection to the database
+ *
+ * @since 21.0.0
+ */
+ public const REASON_CONNECTION_LOST = 1;
+
+ /**
+ * A database constraint was violated
+ *
+ * @since 21.0.0
+ */
+ public const REASON_CONSTRAINT_VIOLATION = 2;
+
+ /**
+ * A database object (table, column, index) already exists
+ *
+ * @since 21.0.0
+ */
+ public const REASON_DATABASE_OBJECT_EXISTS = 3;
+
+ /**
+ * A database object (table, column, index) can't be found
+ *
+ * @since 21.0.0
+ */
+ public const REASON_DATABASE_OBJECT_NOT_FOUND = 4;
+
+ /**
+ * The database ran into a deadlock
+ *
+ * @since 21.0.0
+ */
+ public const REASON_DEADLOCK = 5;
+
+ /**
+ * The database driver encountered an issue
+ *
+ * @since 21.0.0
+ */
+ public const REASON_DRIVER = 6;
+
+ /**
+ * A foreign key constraint was violated
+ *
+ * @since 21.0.0
+ */
+ public const REASON_FOREIGN_KEY_VIOLATION = 7;
+
+ /**
+ * An invalid argument was passed to the database abstraction
+ *
+ * @since 21.0.0
+ */
+ public const REASON_INVALID_ARGUMENT = 8;
+
+ /**
+ * A field name was invalid
+ *
+ * @since 21.0.0
+ */
+ public const REASON_INVALID_FIELD_NAME = 9;
+
+ /**
+ * A name in the query was ambiguous
+ *
+ * @since 21.0.0
+ */
+ public const REASON_NON_UNIQUE_FIELD_NAME = 10;
+
+ /**
+ * A not null contraint was violated
+ *
+ * @since 21.0.0
+ */
+ public const REASON_NOT_NULL_CONSTRAINT_VIOLATION = 11;
+
+ /**
+ * A generic server error was encountered
+ *
+ * @since 21.0.0
+ */
+ public const REASON_SERVER = 12;
+
+ /**
+ * A syntax error was reported by the server
+ *
+ * @since 21.0.0
+ */
+ public const REASON_SYNTAX_ERROR = 13;
+
+ /**
+ * A unique constraint was violated
+ *
+ * @since 21.0.0
+ */
+ public const REASON_UNIQUE_CONSTRAINT_VIOLATION = 14;
+
+ /**
+ * @return int|null
+ * @psalm-return Exception::REASON_*
+ * @since 21.0.0
+ */
+ public function getReason(): ?int {
+ return null;
+ }
+}
diff --git a/lib/public/DB/QueryBuilder/IQueryBuilder.php b/lib/public/DB/QueryBuilder/IQueryBuilder.php
index 8fcbd6c3276..24de7b4ce60 100644
--- a/lib/public/DB/QueryBuilder/IQueryBuilder.php
+++ b/lib/public/DB/QueryBuilder/IQueryBuilder.php
@@ -29,6 +29,7 @@
namespace OCP\DB\QueryBuilder;
use Doctrine\DBAL\Connection;
+use OCP\DB\Exception;
use OCP\DB\IResult;
/**
@@ -154,6 +155,7 @@ interface IQueryBuilder {
* to bridge old code to the new API
*
* @return IResult|int
+ * @throws Exception since 21.0.0
* @since 8.2.0
*/
public function execute();
diff --git a/lib/public/IDBConnection.php b/lib/public/IDBConnection.php
index 16a5f998fde..5618e3ec40b 100644
--- a/lib/public/IDBConnection.php
+++ b/lib/public/IDBConnection.php
@@ -39,8 +39,8 @@
namespace OCP;
-use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Schema\Schema;
+use OCP\DB\Exception;
use OCP\DB\IPreparedStatement;
use OCP\DB\IResult;
use OCP\DB\QueryBuilder\IQueryBuilder;
@@ -103,6 +103,7 @@ interface IDBConnection {
* @param array $types The parameter types.
* @return int The number of affected rows.
* @since 8.0.0
+ * @throws Exception since 21.0.0
*
* @deprecated 21.0.0 use executeStatement
*/
@@ -119,6 +120,7 @@ interface IDBConnection {
* @param array $types The parameter types.
* @return int The number of affected rows.
* @since 21.0.0
+ * @throws Exception since 21.0.0
*/
public function executeStatement($sql, array $params = [], array $types = []): int;
@@ -143,7 +145,7 @@ interface IDBConnection {
* If this is null or an empty array, all keys of $input will be compared
* Please note: text fields (clob) must not be used in the compare array
* @return int number of inserted rows
- * @throws Exception
+ * @throws Exception used to be the removed dbal exception, since 21.0.0 it's \OCP\DB\Exception
* @since 6.0.0 - parameter $compare was added in 8.1.0, return type changed from boolean in 8.1.0
* @deprecated 15.0.0 - use unique index and "try { $db->insert() } catch (UniqueConstraintViolationException $e) {}" instead, because it is more reliable and does not have the risk for deadlocks - see https://github.com/nextcloud/server/pull/12371
*/
@@ -171,7 +173,7 @@ interface IDBConnection {
* @param array $values (column name => value)
* @param array $updatePreconditionValues ensure values match preconditions (column name => value)
* @return int number of new rows
- * @throws Exception
+ * @throws Exception used to be the removed dbal exception, since 21.0.0 it's \OCP\DB\Exception
* @throws PreconditionNotMetException
* @since 9.0.0
*/
@@ -185,6 +187,7 @@ interface IDBConnection {
* transaction while holding a lock.
*
* @param string $tableName
+ * @throws Exception since 21.0.0
* @since 9.1.0
*/
public function lockTable($tableName): void;
@@ -192,6 +195,7 @@ interface IDBConnection {
/**
* Release a previous acquired lock again
*
+ * @throws Exception since 21.0.0
* @since 9.1.0
*/
public function unlockTable(): void;
@@ -255,6 +259,7 @@ interface IDBConnection {
* Establishes the connection with the database.
*
* @return bool
+ * @throws Exception since 21.0.0
* @since 8.0.0
*/
public function connect(): bool;
@@ -288,6 +293,7 @@ interface IDBConnection {
* Drop a table from the database if it exists
*
* @param string $table table name without the prefix
+ * @throws Exception since 21.0.0
* @since 8.0.0
*/
public function dropTable(string $table): void;
@@ -297,6 +303,7 @@ interface IDBConnection {
*
* @param string $table table name without the prefix
* @return bool
+ * @throws Exception since 21.0.0
* @since 8.0.0
*/
public function tableExists(string $table): bool;
@@ -322,6 +329,7 @@ interface IDBConnection {
* Create the schema of the connected database
*
* @return Schema
+ * @throws Exception since 21.0.0
* @since 13.0.0
*/
public function createSchema(): Schema;
@@ -330,6 +338,7 @@ interface IDBConnection {
* Migrate the database to the given schema
*
* @param Schema $toSchema
+ * @throws Exception since 21.0.0
* @since 13.0.0
*/
public function migrateToSchema(Schema $toSchema): void;