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
37 changes: 37 additions & 0 deletions system/Database/BaseConnection.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

use Closure;
use CodeIgniter\Database\Exceptions\DatabaseException;
use CodeIgniter\Database\Exceptions\RetryableTransactionException;
use CodeIgniter\Database\Exceptions\UniqueConstraintViolationException;
use CodeIgniter\Events\Events;
use CodeIgniter\I18n\Time;
use Exception;
Expand Down Expand Up @@ -2138,6 +2140,41 @@ public function getLastException(): ?DatabaseException
return $this->lastException;
}

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

/**
* Checks whether the native database code represents a retryable transaction failure.
*/
protected function isRetryableTransactionErrorCode(int|string $code): bool
{
return false;
}

/**
* Creates the appropriate database exception for a native database error.
*/
protected function createDatabaseException(
string $message,
int|string $code = 0,
?Throwable $previous = null,
): DatabaseException {
if ($this->isUniqueConstraintViolation($code, $message)) {
return new UniqueConstraintViolationException($message, $code, $previous);
}

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

return new DatabaseException($message, $code, $previous);
}

/**
* Insert ID
*
Expand Down
18 changes: 18 additions & 0 deletions system/Database/Exceptions/RetryableTransactionException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?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;

class RetryableTransactionException extends DatabaseException
{
}
24 changes: 19 additions & 5 deletions system/Database/MySQLi/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

use CodeIgniter\Database\BaseConnection;
use CodeIgniter\Database\Exceptions\DatabaseException;
use CodeIgniter\Database\Exceptions\UniqueConstraintViolationException;
use CodeIgniter\Database\TableName;
use CodeIgniter\Exceptions\LogicException;
use mysqli;
Expand Down Expand Up @@ -98,6 +97,24 @@ class Connection extends BaseConnection
*/
protected bool $strictOn = false;

/**
* Checks whether the native database error represents a unique constraint violation.
*/
protected function isUniqueConstraintViolation(int|string $code, string $message): bool
{
// ER_DUP_ENTRY: duplicate key value.
return $code === 1062;
}

/**
* Checks whether the native database code represents a retryable transaction failure.
*/
protected function isRetryableTransactionErrorCode(int|string $code): bool
{
// ER_LOCK_DEADLOCK: InnoDB rolls back the full transaction.
return $code === 1213;
}

/**
* Connect to the database.
*
Expand Down Expand Up @@ -317,10 +334,7 @@ protected function execute(string $sql)
'trace' => render_backtrace($e->getTrace()),
]);

// MySQL error 1062: ER_DUP_ENTRY – duplicate key value
$exception = $e->getCode() === 1062
? new UniqueConstraintViolationException($e->getMessage(), $e->getCode(), $e)
: new DatabaseException($e->getMessage(), $e->getCode(), $e);
$exception = $this->createDatabaseException($e->getMessage(), $e->getCode(), $e);

if ($this->DBDebug) {
throw $exception;
Expand Down
28 changes: 19 additions & 9 deletions system/Database/OCI8/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

use CodeIgniter\Database\BaseConnection;
use CodeIgniter\Database\Exceptions\DatabaseException;
use CodeIgniter\Database\Exceptions\UniqueConstraintViolationException;
use CodeIgniter\Database\Query;
use CodeIgniter\Database\TableName;
use ErrorException;
Expand Down Expand Up @@ -115,6 +114,23 @@ class Connection extends BaseConnection
*/
public $lastInsertedTableName;

/**
* Checks whether the native database error represents a unique constraint violation.
*/
protected function isUniqueConstraintViolation(int|string $code, string $message): bool
{
// ORA-00001: unique constraint violated.
return $code === 1;
}

/**
* Checks whether the native database code represents a retryable transaction failure.
*/
protected function isRetryableTransactionErrorCode(int|string $code): bool
{
return in_array($code, [60, 8177], true);
}

/**
* confirm DSN format.
*/
Expand Down Expand Up @@ -240,11 +256,8 @@ protected function execute(string $sql)
$result = oci_execute($this->stmtId, $this->commitMode) ? $this->stmtId : false;

if ($result === false) {
// ORA-00001: unique constraint violated
$error = $this->error();
$exception = $error['code'] === 1
? new UniqueConstraintViolationException((string) $error['message'], $error['code'])
: new DatabaseException((string) $error['message'], $error['code']);
$exception = $this->createDatabaseException((string) $error['message'], $error['code']);

if ($this->DBDebug) {
throw $exception;
Expand Down Expand Up @@ -272,11 +285,8 @@ protected function execute(string $sql)
'trace' => render_backtrace($trace),
]);

// ORA-00001: unique constraint violated
$error = $this->error();
$exception = $error['code'] === 1
? new UniqueConstraintViolationException((string) $error['message'], $error['code'], $e)
: new DatabaseException((string) $error['message'], $error['code'], $e);
$exception = $this->createDatabaseException((string) $error['message'], $error['code'], $e);

if ($this->DBDebug) {
throw $exception;
Expand Down
21 changes: 17 additions & 4 deletions system/Database/Postgre/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

use CodeIgniter\Database\BaseConnection;
use CodeIgniter\Database\Exceptions\DatabaseException;
use CodeIgniter\Database\Exceptions\UniqueConstraintViolationException;
use CodeIgniter\Database\RawSql;
use CodeIgniter\Database\TableName;
use ErrorException;
Expand Down Expand Up @@ -63,6 +62,22 @@ class Connection extends BaseConnection
*/
private ?PgSqlResult $lastFailedResult = null;

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

/**
* Checks whether the native database code represents a retryable transaction failure.
*/
protected function isRetryableTransactionErrorCode(int|string $code): bool
{
return in_array($code, ['40001', '40P01'], true);
}

/**
* Connect to the database.
*
Expand Down Expand Up @@ -272,9 +287,7 @@ protected function execute(string $sql)
'trace' => render_backtrace($trace),
]);

$exception = $sqlstate === '23505'
? new UniqueConstraintViolationException($message, $sqlstate)
: new DatabaseException($message, $sqlstate);
$exception = $this->createDatabaseException($message, $sqlstate);

if ($this->DBDebug) {
throw $exception;
Expand Down
62 changes: 39 additions & 23 deletions system/Database/SQLSRV/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

use CodeIgniter\Database\BaseConnection;
use CodeIgniter\Database\Exceptions\DatabaseException;
use CodeIgniter\Database\Exceptions\UniqueConstraintViolationException;
use CodeIgniter\Database\TableName;
use stdClass;

Expand Down Expand Up @@ -90,6 +89,44 @@ class Connection extends BaseConnection
*/
protected $_reserved_identifiers = ['*'];

/**
* Checks whether the native database error represents a unique constraint violation.
*/
protected function isUniqueConstraintViolation(int|string $code, string $message): bool
{
$errors = sqlsrv_errors(SQLSRV_ERR_ERRORS);
if (! is_array($errors)) {
return false;
}

foreach ($errors as $error) {
// SQLSTATE 23000 (integrity constraint violation) with SQL Server error
// 2627 (UNIQUE CONSTRAINT or PRIMARY KEY violation) or 2601 (UNIQUE INDEX violation).
if (($error['SQLSTATE'] ?? '') === '23000'
&& in_array($error['code'] ?? 0, [2627, 2601], true)) {
return true;
}
}

return false;
}

/**
* Checks whether the native database code represents a retryable transaction failure.
*/
protected function isRetryableTransactionErrorCode(int|string $code): bool
{
$vendorCode = (string) (is_string($code) && str_contains($code, '/')
? substr($code, strrpos($code, '/') + 1)
: $code);

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

return in_array((int) $vendorCode, [1205, 3960], true);
}

/**
* Class constructor
*/
Expand Down Expand Up @@ -538,9 +575,7 @@ protected function execute(string $sql)
]);

$error = $this->error();
$exception = $this->isUniqueConstraintViolation()
? new UniqueConstraintViolationException($message, $error['code'])
: new DatabaseException($message, $error['code']);
$exception = $this->createDatabaseException($message, $error['code']);

if ($this->DBDebug) {
throw $exception;
Expand All @@ -552,25 +587,6 @@ protected function execute(string $sql)
return $stmt;
}

private function isUniqueConstraintViolation(): bool
{
$errors = sqlsrv_errors(SQLSRV_ERR_ERRORS);
if (! is_array($errors)) {
return false;
}

foreach ($errors as $error) {
// SQLSTATE 23000 (integrity constraint violation) with SQL Server error
// 2627 (UNIQUE CONSTRAINT or PRIMARY KEY violation) or 2601 (UNIQUE INDEX violation).
if (($error['SQLSTATE'] ?? '') === '23000'
&& in_array($error['code'] ?? 0, [2627, 2601], true)) {
return true;
}
}

return false;
}

/**
* The name of the platform in use (MySQLi, mssql, etc)
*/
Expand Down
34 changes: 21 additions & 13 deletions system/Database/SQLite3/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

use CodeIgniter\Database\BaseConnection;
use CodeIgniter\Database\Exceptions\DatabaseException;
use CodeIgniter\Database\Exceptions\UniqueConstraintViolationException;
use CodeIgniter\Database\TableName;
use CodeIgniter\Exceptions\InvalidArgumentException;
use Exception;
Expand Down Expand Up @@ -67,6 +66,26 @@ class Connection extends BaseConnection
*/
protected ?int $synchronous = null;

/**
* Checks whether the native database error represents a unique constraint violation.
*/
protected function isUniqueConstraintViolation(int|string $code, string $message): bool
{
// SQLite3 reports unique violations in two formats depending on version:
// Modern: "UNIQUE constraint failed: table.column"
// Legacy: "column X is not unique"
return str_contains($message, 'UNIQUE constraint failed')
|| str_contains($message, 'is not unique');
}

/**
* Checks whether the native database code represents a retryable transaction failure.
*/
protected function isRetryableTransactionErrorCode(int|string $code): bool
{
return $code === 5;
}

/**
* @return void
*/
Expand Down Expand Up @@ -172,9 +191,7 @@ protected function execute(string $sql)
]);

$error = $this->error();
$exception = $this->isUniqueConstraintViolation($e->getMessage())
? new UniqueConstraintViolationException($e->getMessage(), $error['code'], $e)
: new DatabaseException($e->getMessage(), $error['code'], $e);
$exception = $this->createDatabaseException($e->getMessage(), $error['code'], $e);

if ($this->DBDebug) {
throw $exception;
Expand All @@ -186,15 +203,6 @@ protected function execute(string $sql)
return false;
}

private function isUniqueConstraintViolation(string $message): bool
{
// SQLite3 reports unique violations in two formats depending on version:
// Modern: "UNIQUE constraint failed: table.column"
// Legacy: "column X is not unique"
return str_contains($message, 'UNIQUE constraint failed')
|| str_contains($message, 'is not unique');
}

/**
* Returns the total number of rows affected by this query.
*/
Expand Down
Loading
Loading