Merge pull request #25091 from nextcloud/enhancement/ocp-db-exception-abstraction

Add our own DB exception abstraction
This commit is contained in:
Christoph Wurst 2021-01-14 12:27:23 +01:00 committed by GitHub
commit b9287f9780
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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\\ContactsMenu\\IProvider' => $baseDir . '/lib/public/Contacts/ContactsMenu/IProvider.php',
'OCP\\Contacts\\Events\\ContactInteractedWithEvent' => $baseDir . '/lib/public/Contacts/Events/ContactInteractedWithEvent.php', 'OCP\\Contacts\\Events\\ContactInteractedWithEvent' => $baseDir . '/lib/public/Contacts/Events/ContactInteractedWithEvent.php',
'OCP\\Contacts\\IManager' => $baseDir . '/lib/public/Contacts/IManager.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\\IPreparedStatement' => $baseDir . '/lib/public/DB/IPreparedStatement.php',
'OCP\\DB\\IResult' => $baseDir . '/lib/public/DB/IResult.php', 'OCP\\DB\\IResult' => $baseDir . '/lib/public/DB/IResult.php',
'OCP\\DB\\ISchemaWrapper' => $baseDir . '/lib/public/DB/ISchemaWrapper.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\\Connection' => $baseDir . '/lib/private/DB/Connection.php',
'OC\\DB\\ConnectionAdapter' => $baseDir . '/lib/private/DB/ConnectionAdapter.php', 'OC\\DB\\ConnectionAdapter' => $baseDir . '/lib/private/DB/ConnectionAdapter.php',
'OC\\DB\\ConnectionFactory' => $baseDir . '/lib/private/DB/ConnectionFactory.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\\MDB2SchemaManager' => $baseDir . '/lib/private/DB/MDB2SchemaManager.php',
'OC\\DB\\MDB2SchemaReader' => $baseDir . '/lib/private/DB/MDB2SchemaReader.php', 'OC\\DB\\MDB2SchemaReader' => $baseDir . '/lib/private/DB/MDB2SchemaReader.php',
'OC\\DB\\MigrationException' => $baseDir . '/lib/private/DB/MigrationException.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\\ContactsMenu\\IProvider' => __DIR__ . '/../../..' . '/lib/public/Contacts/ContactsMenu/IProvider.php',
'OCP\\Contacts\\Events\\ContactInteractedWithEvent' => __DIR__ . '/../../..' . '/lib/public/Contacts/Events/ContactInteractedWithEvent.php', 'OCP\\Contacts\\Events\\ContactInteractedWithEvent' => __DIR__ . '/../../..' . '/lib/public/Contacts/Events/ContactInteractedWithEvent.php',
'OCP\\Contacts\\IManager' => __DIR__ . '/../../..' . '/lib/public/Contacts/IManager.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\\IPreparedStatement' => __DIR__ . '/../../..' . '/lib/public/DB/IPreparedStatement.php',
'OCP\\DB\\IResult' => __DIR__ . '/../../..' . '/lib/public/DB/IResult.php', 'OCP\\DB\\IResult' => __DIR__ . '/../../..' . '/lib/public/DB/IResult.php',
'OCP\\DB\\ISchemaWrapper' => __DIR__ . '/../../..' . '/lib/public/DB/ISchemaWrapper.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\\Connection' => __DIR__ . '/../../..' . '/lib/private/DB/Connection.php',
'OC\\DB\\ConnectionAdapter' => __DIR__ . '/../../..' . '/lib/private/DB/ConnectionAdapter.php', 'OC\\DB\\ConnectionAdapter' => __DIR__ . '/../../..' . '/lib/private/DB/ConnectionAdapter.php',
'OC\\DB\\ConnectionFactory' => __DIR__ . '/../../..' . '/lib/private/DB/ConnectionFactory.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\\MDB2SchemaManager' => __DIR__ . '/../../..' . '/lib/private/DB/MDB2SchemaManager.php',
'OC\\DB\\MDB2SchemaReader' => __DIR__ . '/../../..' . '/lib/private/DB/MDB2SchemaReader.php', 'OC\\DB\\MDB2SchemaReader' => __DIR__ . '/../../..' . '/lib/private/DB/MDB2SchemaReader.php',
'OC\\DB\\MigrationException' => __DIR__ . '/../../..' . '/lib/private/DB/MigrationException.php', 'OC\\DB\\MigrationException' => __DIR__ . '/../../..' . '/lib/private/DB/MigrationException.php',

View File

@ -30,6 +30,7 @@
namespace OC\DB; namespace OC\DB;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
/** /**
@ -49,7 +50,9 @@ class Adapter {
/** /**
* @param string $table name * @param string $table name
*
* @return int id of last insert statement * @return int id of last insert statement
* @throws Exception
*/ */
public function lastInsertId($table) { public function lastInsertId($table) {
return (int) $this->conn->realLastInsertId($table); return (int) $this->conn->realLastInsertId($table);
@ -67,6 +70,7 @@ class Adapter {
* Create an exclusive read+write lock on a table * Create an exclusive read+write lock on a table
* *
* @param string $tableName * @param string $tableName
* @throws Exception
* @since 9.1.0 * @since 9.1.0
*/ */
public function lockTable($tableName) { public function lockTable($tableName) {
@ -77,6 +81,7 @@ class Adapter {
/** /**
* Release a previous acquired lock again * Release a previous acquired lock again
* *
* @throws Exception
* @since 9.1.0 * @since 9.1.0
*/ */
public function unlockTable() { public function unlockTable() {
@ -94,7 +99,7 @@ class Adapter {
* If this is null or an empty array, all keys of $input will be compared * 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 * Please note: text fields (clob) must not be used in the compare array
* @return int number of inserted rows * @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 * @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) { 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 { public function insertIgnoreConflict(string $table,array $values) : int {
try { try {
$builder = $this->conn->getQueryBuilder(); $builder = $this->conn->getQueryBuilder();

View File

@ -74,6 +74,9 @@ class Connection extends ReconnectWrapper {
/** @var int */ /** @var int */
protected $queriesExecuted = 0; protected $queriesExecuted = 0;
/**
* @throws Exception
*/
public function connect() { public function connect() {
try { try {
return parent::connect(); return parent::connect();
@ -183,7 +186,9 @@ class Connection extends ReconnectWrapper {
* @param string $statement The SQL statement to prepare. * @param string $statement The SQL statement to prepare.
* @param int $limit * @param int $limit
* @param int $offset * @param int $offset
*
* @return Statement The prepared statement. * @return Statement The prepared statement.
* @throws Exception
*/ */
public function prepare($statement, $limit = null, $offset = null): Statement { public function prepare($statement, $limit = null, $offset = null): Statement {
if ($limit === -1) { if ($limit === -1) {
@ -221,6 +226,9 @@ class Connection extends ReconnectWrapper {
return parent::executeQuery($sql, $params, $types, $qcp); return parent::executeQuery($sql, $params, $types, $qcp);
} }
/**
* @throws Exception
*/
public function executeUpdate(string $sql, array $params = [], array $types = []): int { public function executeUpdate(string $sql, array $params = [], array $types = []): int {
$sql = $this->replaceTablePrefix($sql); $sql = $this->replaceTablePrefix($sql);
$sql = $this->adapter->fixupStatement($sql); $sql = $this->adapter->fixupStatement($sql);
@ -258,7 +266,9 @@ class Connection extends ReconnectWrapper {
* columns or sequences. * columns or sequences.
* *
* @param string $seqName Name of the sequence object from which the ID should be returned. * @param string $seqName Name of the sequence object from which the ID should be returned.
*
* @return string the last inserted ID. * @return string the last inserted ID.
* @throws Exception
*/ */
public function lastInsertId($seqName = null) { public function lastInsertId($seqName = null) {
if ($seqName) { if ($seqName) {
@ -267,7 +277,10 @@ class Connection extends ReconnectWrapper {
return $this->adapter->lastInsertId($seqName); return $this->adapter->lastInsertId($seqName);
} }
// internal use /**
* @internal
* @throws Exception
*/
public function realLastInsertId($seqName = null) { public function realLastInsertId($seqName = null) {
return parent::lastInsertId($seqName); return parent::lastInsertId($seqName);
} }
@ -364,7 +377,9 @@ class Connection extends ReconnectWrapper {
* Create an exclusive read+write lock on a table * Create an exclusive read+write lock on a table
* *
* @param string $tableName * @param string $tableName
*
* @throws \BadMethodCallException When trying to acquire a second lock * @throws \BadMethodCallException When trying to acquire a second lock
* @throws Exception
* @since 9.1.0 * @since 9.1.0
*/ */
public function lockTable($tableName) { public function lockTable($tableName) {
@ -380,6 +395,7 @@ class Connection extends ReconnectWrapper {
/** /**
* Release a previous acquired lock again * Release a previous acquired lock again
* *
* @throws Exception
* @since 9.1.0 * @since 9.1.0
*/ */
public function unlockTable() { public function unlockTable() {
@ -415,6 +431,8 @@ class Connection extends ReconnectWrapper {
* Drop a table from the database if it exists * Drop a table from the database if it exists
* *
* @param string $table table name without the prefix * @param string $table table name without the prefix
*
* @throws Exception
*/ */
public function dropTable($table) { public function dropTable($table) {
$table = $this->tablePrefix . trim($table); $table = $this->tablePrefix . trim($table);
@ -428,7 +446,9 @@ class Connection extends ReconnectWrapper {
* Check if a table exists * Check if a table exists
* *
* @param string $table table name without the prefix * @param string $table table name without the prefix
*
* @return bool * @return bool
* @throws Exception
*/ */
public function tableExists($table) { public function tableExists($table) {
$table = $this->tablePrefix . trim($table); $table = $this->tablePrefix . trim($table);
@ -483,6 +503,7 @@ class Connection extends ReconnectWrapper {
* Create the schema of the connected database * Create the schema of the connected database
* *
* @return Schema * @return Schema
* @throws Exception
*/ */
public function createSchema() { public function createSchema() {
$schemaManager = new MDB2SchemaManager($this); $schemaManager = new MDB2SchemaManager($this);
@ -494,6 +515,8 @@ class Connection extends ReconnectWrapper {
* Migrate the database to the given schema * Migrate the database to the given schema
* *
* @param Schema $toSchema * @param Schema $toSchema
*
* @throws Exception
*/ */
public function migrateToSchema(Schema $toSchema) { public function migrateToSchema(Schema $toSchema) {
$schemaManager = new MDB2SchemaManager($this); $schemaManager = new MDB2SchemaManager($this);

View File

@ -25,8 +25,10 @@ declare(strict_types=1);
namespace OC\DB; namespace OC\DB;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Schema;
use OC\DB\Exceptions\DbalException;
use OCP\DB\IPreparedStatement; use OCP\DB\IPreparedStatement;
use OCP\DB\IResult; use OCP\DB\IResult;
use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\DB\QueryBuilder\IQueryBuilder;
@ -49,51 +51,95 @@ class ConnectionAdapter implements IDBConnection {
} }
public function prepare($sql, $limit = null, $offset = null): IPreparedStatement { public function prepare($sql, $limit = null, $offset = null): IPreparedStatement {
return new PreparedStatement( try {
$this->inner->prepare($sql, $limit, $offset) 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 { public function executeQuery(string $sql, array $params = [], $types = []): IResult {
return new ResultAdapter( try {
$this->inner->executeQuery($sql, $params, $types) 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 { 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 { 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 { 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) { 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 { 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 { 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 { 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 { public function unlockTable(): void {
$this->inner->unlockTable(); try {
$this->inner->unlockTable();
} catch (Exception $e) {
throw DbalException::wrap($e);
}
} }
public function beginTransaction(): void { public function beginTransaction(): void {
$this->inner->beginTransaction(); try {
$this->inner->beginTransaction();
} catch (Exception $e) {
throw DbalException::wrap($e);
}
} }
public function inTransaction(): bool { public function inTransaction(): bool {
@ -101,11 +147,19 @@ class ConnectionAdapter implements IDBConnection {
} }
public function commit(): void { public function commit(): void {
$this->inner->commit(); try {
$this->inner->commit();
} catch (Exception $e) {
throw DbalException::wrap($e);
}
} }
public function rollBack(): void { public function rollBack(): void {
$this->inner->rollBack(); try {
$this->inner->rollBack();
} catch (Exception $e) {
throw DbalException::wrap($e);
}
} }
public function getError(): string { public function getError(): string {
@ -121,7 +175,11 @@ class ConnectionAdapter implements IDBConnection {
} }
public function connect(): bool { public function connect(): bool {
return $this->inner->connect(); try {
return $this->inner->connect();
} catch (Exception $e) {
throw DbalException::wrap($e);
}
} }
public function close(): void { public function close(): void {
@ -140,11 +198,19 @@ class ConnectionAdapter implements IDBConnection {
} }
public function dropTable(string $table): void { 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 { 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 { public function escapeLikeParameter(string $param): string {
@ -159,11 +225,19 @@ class ConnectionAdapter implements IDBConnection {
* @todo leaks a 3rdparty type * @todo leaks a 3rdparty type
*/ */
public function createSchema(): Schema { 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 { 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 { 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 * @param \Doctrine\DBAL\Schema\Schema $targetSchema
*
* @throws Exception
*/ */
public function migrate(Schema $targetSchema) { public function migrate(Schema $targetSchema) {
$this->noEmit = true; $this->noEmit = true;
@ -171,6 +173,9 @@ class Migrator {
return new Table($newName, $table->getColumns(), $newIndexes, [], [], $table->getOptions()); return new Table($newName, $table->getColumns(), $newIndexes, [], [], $table->getOptions());
} }
/**
* @throws Exception
*/
public function createSchema() { public function createSchema() {
$this->connection->getConfiguration()->setSchemaAssetsFilter(function ($asset) { $this->connection->getConfiguration()->setSchemaAssetsFilter(function ($asset) {
/** @var string|AbstractAsset $asset */ /** @var string|AbstractAsset $asset */
@ -231,6 +236,8 @@ class Migrator {
/** /**
* @param \Doctrine\DBAL\Schema\Schema $targetSchema * @param \Doctrine\DBAL\Schema\Schema $targetSchema
* @param \Doctrine\DBAL\Connection $connection * @param \Doctrine\DBAL\Connection $connection
*
* @throws Exception
*/ */
protected function applySchema(Schema $targetSchema, \Doctrine\DBAL\Connection $connection = null) { protected function applySchema(Schema $targetSchema, \Doctrine\DBAL\Connection $connection = null) {
if (is_null($connection)) { 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; namespace OCP\DB\QueryBuilder;
use Doctrine\DBAL\Connection; use Doctrine\DBAL\Connection;
use OCP\DB\Exception;
use OCP\DB\IResult; use OCP\DB\IResult;
/** /**
@ -154,6 +155,7 @@ interface IQueryBuilder {
* to bridge old code to the new API * to bridge old code to the new API
* *
* @return IResult|int * @return IResult|int
* @throws Exception since 21.0.0
* @since 8.2.0 * @since 8.2.0
*/ */
public function execute(); public function execute();

View File

@ -39,8 +39,8 @@
namespace OCP; namespace OCP;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Schema;
use OCP\DB\Exception;
use OCP\DB\IPreparedStatement; use OCP\DB\IPreparedStatement;
use OCP\DB\IResult; use OCP\DB\IResult;
use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\DB\QueryBuilder\IQueryBuilder;
@ -103,6 +103,7 @@ interface IDBConnection {
* @param array $types The parameter types. * @param array $types The parameter types.
* @return int The number of affected rows. * @return int The number of affected rows.
* @since 8.0.0 * @since 8.0.0
* @throws Exception since 21.0.0
* *
* @deprecated 21.0.0 use executeStatement * @deprecated 21.0.0 use executeStatement
*/ */
@ -119,6 +120,7 @@ interface IDBConnection {
* @param array $types The parameter types. * @param array $types The parameter types.
* @return int The number of affected rows. * @return int The number of affected rows.
* @since 21.0.0 * @since 21.0.0
* @throws Exception since 21.0.0
*/ */
public function executeStatement($sql, array $params = [], array $types = []): int; 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 * 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 * Please note: text fields (clob) must not be used in the compare array
* @return int number of inserted rows * @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 * @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 * @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 $values (column name => value)
* @param array $updatePreconditionValues ensure values match preconditions (column name => value) * @param array $updatePreconditionValues ensure values match preconditions (column name => value)
* @return int number of new rows * @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 * @throws PreconditionNotMetException
* @since 9.0.0 * @since 9.0.0
*/ */
@ -185,6 +187,7 @@ interface IDBConnection {
* transaction while holding a lock. * transaction while holding a lock.
* *
* @param string $tableName * @param string $tableName
* @throws Exception since 21.0.0
* @since 9.1.0 * @since 9.1.0
*/ */
public function lockTable($tableName): void; public function lockTable($tableName): void;
@ -192,6 +195,7 @@ interface IDBConnection {
/** /**
* Release a previous acquired lock again * Release a previous acquired lock again
* *
* @throws Exception since 21.0.0
* @since 9.1.0 * @since 9.1.0
*/ */
public function unlockTable(): void; public function unlockTable(): void;
@ -255,6 +259,7 @@ interface IDBConnection {
* Establishes the connection with the database. * Establishes the connection with the database.
* *
* @return bool * @return bool
* @throws Exception since 21.0.0
* @since 8.0.0 * @since 8.0.0
*/ */
public function connect(): bool; public function connect(): bool;
@ -288,6 +293,7 @@ interface IDBConnection {
* Drop a table from the database if it exists * Drop a table from the database if it exists
* *
* @param string $table table name without the prefix * @param string $table table name without the prefix
* @throws Exception since 21.0.0
* @since 8.0.0 * @since 8.0.0
*/ */
public function dropTable(string $table): void; public function dropTable(string $table): void;
@ -297,6 +303,7 @@ interface IDBConnection {
* *
* @param string $table table name without the prefix * @param string $table table name without the prefix
* @return bool * @return bool
* @throws Exception since 21.0.0
* @since 8.0.0 * @since 8.0.0
*/ */
public function tableExists(string $table): bool; public function tableExists(string $table): bool;
@ -322,6 +329,7 @@ interface IDBConnection {
* Create the schema of the connected database * Create the schema of the connected database
* *
* @return Schema * @return Schema
* @throws Exception since 21.0.0
* @since 13.0.0 * @since 13.0.0
*/ */
public function createSchema(): Schema; public function createSchema(): Schema;
@ -330,6 +338,7 @@ interface IDBConnection {
* Migrate the database to the given schema * Migrate the database to the given schema
* *
* @param Schema $toSchema * @param Schema $toSchema
* @throws Exception since 21.0.0
* @since 13.0.0 * @since 13.0.0
*/ */
public function migrateToSchema(Schema $toSchema): void; public function migrateToSchema(Schema $toSchema): void;