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>pull/25091/head
parent
c8cbb73c05
commit
2c9cdc1cdb
@ -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; |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
Loading…
Reference in new issue