Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions system/Database/BaseConnection.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@

use BackedEnum;
use Closure;
use CodeIgniter\Database\Exceptions\CheckConstraintViolationException;
use CodeIgniter\Database\Exceptions\ConstraintViolationException;
use CodeIgniter\Database\Exceptions\DatabaseException;
use CodeIgniter\Database\Exceptions\ForeignKeyConstraintViolationException;
use CodeIgniter\Database\Exceptions\NotNullConstraintViolationException;
use CodeIgniter\Database\Exceptions\RetryableTransactionException;
use CodeIgniter\Database\Exceptions\UniqueConstraintViolationException;
use CodeIgniter\Events\Events;
Expand Down Expand Up @@ -2231,6 +2235,38 @@ protected function isUniqueConstraintViolation(int|string $code, string $message
return false;
}

/**
* Checks whether the native database error represents a foreign key constraint violation.
*/
protected function isForeignKeyConstraintViolation(int|string $code, string $message): bool
{
return false;
}

/**
* Checks whether the native database error represents a NOT NULL constraint violation.
*/
protected function isNotNullConstraintViolation(int|string $code, string $message): bool
{
return false;
}

/**
* Checks whether the native database error represents a CHECK constraint violation.
*/
protected function isCheckConstraintViolation(int|string $code, string $message): bool
{
return false;
}

/**
* Checks whether the native database error represents a constraint violation.
*/
protected function isConstraintViolation(int|string $code, string $message): bool
{
return false;
}

/**
* Checks whether the native database code represents a retryable transaction failure.
*/
Expand All @@ -2253,6 +2289,22 @@ public function createDatabaseException(
return new UniqueConstraintViolationException($message, $code, $previous);
}

if ($this->isForeignKeyConstraintViolation($code, $message)) {
return new ForeignKeyConstraintViolationException($message, $code, $previous);
}

if ($this->isNotNullConstraintViolation($code, $message)) {
return new NotNullConstraintViolationException($message, $code, $previous);
}

if ($this->isCheckConstraintViolation($code, $message)) {
return new CheckConstraintViolationException($message, $code, $previous);
}

if ($this->isConstraintViolation($code, $message)) {
return new ConstraintViolationException($message, $code, $previous);
}

if ($this->isRetryableTransactionErrorCode($code)) {
return new RetryableTransactionException($message, $code, $previous);
}
Expand Down
21 changes: 21 additions & 0 deletions system/Database/Exceptions/CheckConstraintViolationException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/

namespace CodeIgniter\Database\Exceptions;

/**
* Thrown when a CHECK constraint is violated.
*/
class CheckConstraintViolationException extends ConstraintViolationException
{
}
21 changes: 21 additions & 0 deletions system/Database/Exceptions/ConstraintViolationException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/

namespace CodeIgniter\Database\Exceptions;

/**
* Thrown when a database integrity constraint is violated.
*/
class ConstraintViolationException extends DatabaseException
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/

namespace CodeIgniter\Database\Exceptions;

/**
* Thrown when a foreign key constraint is violated.
*/
class ForeignKeyConstraintViolationException extends ConstraintViolationException
{
}
21 changes: 21 additions & 0 deletions system/Database/Exceptions/NotNullConstraintViolationException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/

namespace CodeIgniter\Database\Exceptions;

/**
* Thrown when a NOT NULL constraint is violated.
*/
class NotNullConstraintViolationException extends ConstraintViolationException
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@

namespace CodeIgniter\Database\Exceptions;

class UniqueConstraintViolationException extends DatabaseException
/**
* Thrown when a unique constraint is violated.
*/
class UniqueConstraintViolationException extends ConstraintViolationException
{
}
31 changes: 31 additions & 0 deletions system/Database/MySQLi/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,37 @@ protected function isUniqueConstraintViolation(int|string $code, string $message
return $code === 1062;
}

/**
* Checks whether the native database error represents a foreign key constraint violation.
*/
protected function isForeignKeyConstraintViolation(int|string $code, string $message): bool
{
// ER_NO_REFERENCED_ROW, ER_ROW_IS_REFERENCED, ER_ROW_IS_REFERENCED_2, ER_NO_REFERENCED_ROW_2.
return in_array($code, [1216, 1217, 1451, 1452], true);
}

/**
* Checks whether the native database error represents a NOT NULL constraint violation.
*/
protected function isNotNullConstraintViolation(int|string $code, string $message): bool
{
// ER_BAD_NULL_ERROR, ER_BAD_NULL_ERROR_NOT_IGNORED: column cannot be null.
return in_array($code, [1048, 3673], true);
}

/**
* Checks whether the native database error represents a CHECK constraint violation.
*/
protected function isCheckConstraintViolation(int|string $code, string $message): bool
{
if ($code === 3819) {
return true;
}

// MariaDB reports CHECK failures as ER_CONSTRAINT_FAILED, while MySQL uses 4025 for other errors.
return $code === 4025 && str_contains(strtolower($this->getVersion()), 'mariadb');
}

/**
* Checks whether the native database code represents a retryable transaction failure.
*/
Expand Down
27 changes: 27 additions & 0 deletions system/Database/OCI8/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,33 @@ protected function isUniqueConstraintViolation(int|string $code, string $message
return (int) $code === 1;
}

/**
* Checks whether the native database error represents a foreign key constraint violation.
*/
protected function isForeignKeyConstraintViolation(int|string $code, string $message): bool
{
// ORA-02291: parent key not found; ORA-02292: child record found.
return in_array((int) $code, [2291, 2292], true);
}

/**
* Checks whether the native database error represents a NOT NULL constraint violation.
*/
protected function isNotNullConstraintViolation(int|string $code, string $message): bool
{
// ORA-01400: cannot insert NULL; ORA-01407: cannot update to NULL.
return in_array((int) $code, [1400, 1407], true);
}

/**
* Checks whether the native database error represents a CHECK constraint violation.
*/
protected function isCheckConstraintViolation(int|string $code, string $message): bool
{
// ORA-02290: check constraint violated.
return (int) $code === 2290;
}

/**
* Checks whether the native database code represents a retryable transaction failure.
*/
Expand Down
32 changes: 32 additions & 0 deletions system/Database/Postgre/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,38 @@ protected function isUniqueConstraintViolation(int|string $code, string $message
return $code === '23505';
}

/**
* Checks whether the native database error represents a foreign key constraint violation.
*/
protected function isForeignKeyConstraintViolation(int|string $code, string $message): bool
{
return $code === '23503';
}

/**
* Checks whether the native database error represents a NOT NULL constraint violation.
*/
protected function isNotNullConstraintViolation(int|string $code, string $message): bool
{
return $code === '23502';
}

/**
* Checks whether the native database error represents a CHECK constraint violation.
*/
protected function isCheckConstraintViolation(int|string $code, string $message): bool
{
return $code === '23514';
}

/**
* Checks whether the native database error represents a constraint violation.
*/
protected function isConstraintViolation(int|string $code, string $message): bool
{
return is_string($code) && str_starts_with($code, '23');
}

/**
* Checks whether the native database code represents a retryable transaction failure.
*/
Expand Down
53 changes: 42 additions & 11 deletions system/Database/SQLSRV/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,10 @@ class Connection extends BaseConnection
*/
protected function isUniqueConstraintViolation(int|string $code, string $message): bool
{
$code = (string) $code;
$vendorCode = $this->getVendorErrorCode($code);

if (str_contains($code, '/')) {
[$sqlstate, $vendorCode] = explode('/', $code, 2);

if ($sqlstate === '23000' && in_array((int) $vendorCode, [2627, 2601], true)) {
return true;
}
if ($vendorCode !== null && in_array($vendorCode, [2627, 2601], true)) {
return $this->hasSQLState($code, '23000');
}

$errors = sqlsrv_errors(SQLSRV_ERR_ERRORS);
Expand All @@ -121,20 +117,55 @@ protected function isUniqueConstraintViolation(int|string $code, string $message
return false;
}

/**
* Checks whether the native database error represents a NOT NULL constraint violation.
*/
protected function isNotNullConstraintViolation(int|string $code, string $message): bool
{
return $this->getVendorErrorCode($code) === 515
&& $this->hasSQLState($code, '23000');
}

/**
* Checks whether the native database error represents a constraint violation.
*/
protected function isConstraintViolation(int|string $code, string $message): bool
{
return $this->getSQLState($code) === '23000'
|| ($this->getVendorErrorCode($code) === 547 && $this->hasSQLState($code, '23000'));
}

/**
* Checks whether the native database code represents a retryable transaction failure.
*/
protected function isRetryableTransactionErrorCode(int|string $code): bool
{
$vendorCode = $this->getVendorErrorCode($code);

return $vendorCode !== null && in_array($vendorCode, [1205, 3960], true);
}

private function getVendorErrorCode(int|string $code): ?int
{
$vendorCode = (string) (is_string($code) && str_contains($code, '/')
? substr($code, strrpos($code, '/') + 1)
: $code);

if (preg_match('/^\d+$/', $vendorCode) !== 1) {
return false;
}
return preg_match('/^\d+$/', $vendorCode) === 1 ? (int) $vendorCode : null;
}

private function getSQLState(int|string $code): string
{
return is_string($code) && str_contains($code, '/')
? substr($code, 0, strpos($code, '/'))
: (string) $code;
}

return in_array((int) $vendorCode, [1205, 3960], true);
private function hasSQLState(int|string $code, string $sqlstate): bool
{
return ! is_string($code)
|| ! str_contains($code, '/')
|| $this->getSQLState($code) === $sqlstate;
}

/**
Expand Down
Loading
Loading