Add our own DB exception abstraction

Right now our API exports the Doctrine/dbal exception. As we've seen
with the dbal 3 upgrade, the leakage of 3rdparty types is problematic as
a dependency update means lots of work in apps, due to the direct
dependency of what Nextcloud ships. This breaks this dependency so that
apps only need to depend on our public API. That API can then be vendor
(db lib) agnostic and we can work around future deprecations/removals in
dbal more easily.

Right now the type of exception thrown is transported as "reason". For
the more popular types of errors we can extend the new exception class
and allow apps to catch specific errors only. Right now they have to
catch-check-rethrow. This is not ideal, but better than the dependnecy
on dbal.

Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
This commit is contained in:
Christoph Wurst 2021-01-12 12:24:36 +01:00
parent c8cbb73c05
commit 2c9cdc1cdb
No known key found for this signature in database
GPG Key ID: CC42AC2A7F0E56D8
10 changed files with 439 additions and 27 deletions

View File

@ -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',
@ -952,6 +953,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',

View File

@ -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',
@ -981,6 +982,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',

View File

@ -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();

View File

@ -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);

View File

@ -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 {

View File

@ -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;
}
}

View File

@ -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)) {

149
lib/public/DB/Exception.php Normal file
View File

@ -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;
}
}

View File

@ -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();

View File

@ -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;