1
0
mirror of https://github.com/dg/dibi.git synced 2025-08-04 21:28:02 +02:00

implemented DriverException descendants:

- ConstraintViolationException
- ForeignKeyConstraintViolationException
- NotNullConstraintViolationException
- UniqueConstraintViolationException
This commit is contained in:
David Grudl
2015-10-13 18:08:04 +02:00
parent 3f44e96353
commit fbb63a9cc3
14 changed files with 455 additions and 21 deletions

View File

@@ -157,8 +157,8 @@ class MySqlDriver implements Dibi\Driver, Dibi\ResultDriver
$res = @mysql_unbuffered_query($sql, $this->connection); // intentionally @ $res = @mysql_unbuffered_query($sql, $this->connection); // intentionally @
} }
if (mysql_errno($this->connection)) { if ($code = mysql_errno($this->connection)) {
throw new Dibi\DriverException(mysql_error($this->connection), mysql_errno($this->connection), $sql); throw MySqliDriver::createException(mysql_error($this->connection), $code, $sql);
} elseif (is_resource($res)) { } elseif (is_resource($res)) {
return $this->createResultDriver($res); return $this->createResultDriver($res);

View File

@@ -152,8 +152,8 @@ class MySqliDriver implements Dibi\Driver, Dibi\ResultDriver
{ {
$res = @mysqli_query($this->connection, $sql, $this->buffered ? MYSQLI_STORE_RESULT : MYSQLI_USE_RESULT); // intentionally @ $res = @mysqli_query($this->connection, $sql, $this->buffered ? MYSQLI_STORE_RESULT : MYSQLI_USE_RESULT); // intentionally @
if (mysqli_errno($this->connection)) { if ($code = mysqli_errno($this->connection)) {
throw new Dibi\DriverException(mysqli_error($this->connection), mysqli_errno($this->connection), $sql); throw self::createException(mysqli_error($this->connection), $code, $sql);
} elseif (is_object($res)) { } elseif (is_object($res)) {
return $this->createResultDriver($res); return $this->createResultDriver($res);
@@ -161,6 +161,29 @@ class MySqliDriver implements Dibi\Driver, Dibi\ResultDriver
} }
/**
* @return Dibi\DriverException
*/
public static function createException($message, $code, $sql)
{
if (in_array($code, [1216, 1217, 1451, 1452, 1701], TRUE)) {
return new Dibi\ForeignKeyConstraintViolationException($message, $code, $sql);
} elseif (in_array($code, [1062, 1557, 1569, 1586], TRUE)) {
return new Dibi\UniqueConstraintViolationException($message, $code, $sql);
} elseif ($code >= 2001 && $code <= 2028) {
return new Dibi\ConnectionException($message, $code, $sql);
} elseif (in_array($code, [1048, 1121, 1138, 1171, 1252, 1263, 1566], TRUE)) {
return new Dibi\NotNullConstraintViolationException($message, $code, $sql);
} else {
return new Dibi\DriverException($message, $code, $sql);
}
}
/** /**
* Retrieves information about the most recently executed query. * Retrieves information about the most recently executed query.
* @return array * @return array

View File

@@ -109,7 +109,7 @@ class OracleDriver implements Dibi\Driver, Dibi\ResultDriver, Dibi\Reflector
@oci_execute($res, $this->autocommit ? OCI_COMMIT_ON_SUCCESS : OCI_DEFAULT); @oci_execute($res, $this->autocommit ? OCI_COMMIT_ON_SUCCESS : OCI_DEFAULT);
$err = oci_error($res); $err = oci_error($res);
if ($err) { if ($err) {
throw new Dibi\DriverException($err['message'], $err['code'], $sql); throw self::createException($err['message'], $err['code'], $sql);
} elseif (is_resource($res)) { } elseif (is_resource($res)) {
return $this->createResultDriver($res); return $this->createResultDriver($res);
@@ -121,6 +121,26 @@ class OracleDriver implements Dibi\Driver, Dibi\ResultDriver, Dibi\Reflector
} }
/**
* @return Dibi\DriverException
*/
public static function createException($message, $code, $sql)
{
if (in_array($code, [1, 2299, 38911], TRUE)) {
return new Dibi\UniqueConstraintViolationException($message, $code, $sql);
} elseif (in_array($code, [1400], TRUE)) {
return new Dibi\NotNullConstraintViolationException($message, $code, $sql);
} elseif (in_array($code, [2266, 2291, 2292], TRUE)) {
return new Dibi\ForeignKeyConstraintViolationException($message, $code, $sql);
} else {
return new Dibi\DriverException($message, $code, $sql);
}
}
/** /**
* Gets the number of affected rows by the last INSERT, UPDATE or DELETE query. * Gets the number of affected rows by the last INSERT, UPDATE or DELETE query.
* @return int|FALSE number of rows or FALSE on error * @return int|FALSE number of rows or FALSE on error

View File

@@ -111,22 +111,34 @@ class PdoDriver implements Dibi\Driver, Dibi\ResultDriver
if (isset($list[$cmd])) { if (isset($list[$cmd])) {
$this->affectedRows = $this->connection->exec($sql); $this->affectedRows = $this->connection->exec($sql);
if ($this->affectedRows !== FALSE) {
if ($this->affectedRows === FALSE) { return;
$err = $this->connection->errorInfo();
throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1], $sql);
} }
} else { } else {
$res = $this->connection->query($sql); $res = $this->connection->query($sql);
if ($res) {
if ($res === FALSE) {
$err = $this->connection->errorInfo();
throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1], $sql);
} else {
return $this->createResultDriver($res); return $this->createResultDriver($res);
} }
} }
list($sqlState, $code, $message) = $this->connection->errorInfo();
$message = "SQLSTATE[$sqlState]: $message";
switch ($this->driverName) {
case 'mysql':
throw MySqliDriver::createException($message, $code, $sql);
case 'oci':
throw OracleDriver::createException($message, $code, $sql);
case 'pgsql':
throw PostgreDriver::createException($message, $sqlState, $sql);
case 'sqlite':
throw Sqlite3Driver::createException($message, $code, $sql);
default:
throw new Dibi\DriverException($message, $code, $sql);
}
} }
@@ -356,7 +368,7 @@ class PdoDriver implements Dibi\Driver, Dibi\ResultDriver
return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'"); return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
default: default:
throw new DibiNotImplementedException; throw new Dibi\NotImplementedException;
} }
} }

View File

@@ -96,7 +96,7 @@ class PostgreDriver implements Dibi\Driver, Dibi\ResultDriver, Dibi\Reflector
pg_set_error_verbosity($this->connection, PGSQL_ERRORS_VERBOSE); pg_set_error_verbosity($this->connection, PGSQL_ERRORS_VERBOSE);
if (isset($config['charset']) && pg_set_client_encoding($this->connection, $config['charset'])) { if (isset($config['charset']) && pg_set_client_encoding($this->connection, $config['charset'])) {
throw new Dibi\DriverException(pg_last_error($this->connection), 0, $sql); throw self::createException(pg_last_error($this->connection));
} }
if (isset($config['schema'])) { if (isset($config['schema'])) {
@@ -137,7 +137,7 @@ class PostgreDriver implements Dibi\Driver, Dibi\ResultDriver, Dibi\Reflector
$res = @pg_query($this->connection, $sql); // intentionally @ $res = @pg_query($this->connection, $sql); // intentionally @
if ($res === FALSE) { if ($res === FALSE) {
throw new Dibi\DriverException(pg_last_error($this->connection), 0, $sql); throw self::createException(pg_last_error($this->connection), NULL, $sql);
} elseif (is_resource($res)) { } elseif (is_resource($res)) {
$this->affectedRows = pg_affected_rows($res); $this->affectedRows = pg_affected_rows($res);
@@ -148,6 +148,34 @@ class PostgreDriver implements Dibi\Driver, Dibi\ResultDriver, Dibi\Reflector
} }
/**
* @return Dibi\DriverException
*/
public static function createException($message, $code = NULL, $sql = NULL)
{
if ($code === NULL && preg_match('#^ERROR:\s+(\S+):\s*#', $message, $m)) {
$code = $m[1];
$message = substr($message, strlen($m[0]));
}
if ($code === '0A000' && strpos($message, 'truncate') !== FALSE) {
return new Dibi\ForeignKeyConstraintViolationException($message, $code, $sql);
} elseif ($code === '23502') {
return new Dibi\NotNullConstraintViolationException($message, $code, $sql);
} elseif ($code === '23503') {
return new Dibi\ForeignKeyConstraintViolationException($message, $code, $sql);
} elseif ($code === '23505') {
return new Dibi\UniqueConstraintViolationException($message, $code, $sql);
} else {
return new Dibi\DriverException($message, $code, $sql);
}
}
/** /**
* Gets the number of affected rows by the last INSERT, UPDATE or DELETE query. * Gets the number of affected rows by the last INSERT, UPDATE or DELETE query.
* @return int|FALSE number of rows or FALSE on error * @return int|FALSE number of rows or FALSE on error

View File

@@ -112,8 +112,8 @@ class Sqlite3Driver implements Dibi\Driver, Dibi\ResultDriver
} }
$res = @$this->connection->query($sql); // intentionally @ $res = @$this->connection->query($sql); // intentionally @
if ($this->connection->lastErrorCode()) { if ($code = $this->connection->lastErrorCode()) {
throw new Dibi\DriverException($this->connection->lastErrorMsg(), $this->connection->lastErrorCode(), $sql); throw self::createException($this->connection->lastErrorMsg(), $code, $sql);
} elseif ($res instanceof \SQLite3Result) { } elseif ($res instanceof \SQLite3Result) {
return $this->createResultDriver($res); return $this->createResultDriver($res);
@@ -121,6 +121,36 @@ class Sqlite3Driver implements Dibi\Driver, Dibi\ResultDriver
} }
/**
* @return Dibi\DriverException
*/
public static function createException($message, $code, $sql)
{
if ($code !== 19) {
return new Dibi\DriverException($message, $code, $sql);
} elseif (strpos($message, 'must be unique') !== FALSE
|| strpos($message, 'is not unique') !== FALSE
|| strpos($message, 'UNIQUE constraint failed') !== FALSE
) {
return new Dibi\UniqueConstraintViolationException($message, $code, $sql);
} elseif (strpos($message, 'may not be NULL') !== FALSE
|| strpos($message, 'NOT NULL constraint failed') !== FALSE
) {
return new Dibi\NotNullConstraintViolationException($message, $code, $sql);
} elseif (strpos($message, 'foreign key constraint failed') !== FALSE
|| strpos($message, 'FOREIGN KEY constraint failed') !== FALSE
) {
return new Dibi\ForeignKeyConstraintViolationException($message, $code, $sql);
} else {
return new Dibi\ConstraintViolationException($message, $code, $sql);
}
}
/** /**
* Gets the number of affected rows by the last INSERT, UPDATE or DELETE query. * Gets the number of affected rows by the last INSERT, UPDATE or DELETE query.
* @return int|FALSE number of rows or FALSE on error * @return int|FALSE number of rows or FALSE on error

View File

@@ -64,7 +64,7 @@ class DriverException extends Exception
/** /**
* PCRE exception. * PCRE exception.
*/ */
class PcreException extends \Exception class PcreException extends Exception
{ {
use Strict; use Strict;
@@ -125,3 +125,35 @@ class ProcedureException extends Exception
} }
} }
/**
* Base class for all constraint violation related exceptions.
*/
class ConstraintViolationException extends DriverException
{
}
/**
* Exception for a foreign key constraint violation.
*/
class ForeignKeyConstraintViolationException extends ConstraintViolationException
{
}
/**
* Exception for a NOT NULL constraint violation.
*/
class NotNullConstraintViolationException extends ConstraintViolationException
{
}
/**
* Exception for a unique constraint violation.
*/
class UniqueConstraintViolationException extends ConstraintViolationException
{
}

View File

@@ -0,0 +1,41 @@
<?php
/**
* Test: query exceptions.
* @dataProvider ../databases.ini mysql-pdo
*/
use Tester\Assert;
require __DIR__ . '/bootstrap.php';
$conn = new Dibi\Connection($config);
$conn->loadFile(__DIR__ . "/data/$config[system].sql");
$e = Assert::exception(function () use ($conn) {
$conn->query('SELECT');
}, 'Dibi\DriverException', "%a% error in your SQL syntax;%a%", 1064);
Assert::same('SELECT', $e->getSql());
$e = Assert::exception(function () use ($conn) {
$conn->query('INSERT INTO products (product_id, title) VALUES (1, "New")');
}, 'Dibi\UniqueConstraintViolationException', "SQLSTATE[23000]: Duplicate entry '1' for key 'PRIMARY'", 1062);
Assert::same("INSERT INTO products (product_id, title) VALUES (1, 'New')", $e->getSql());
$e = Assert::exception(function () use ($conn) {
$conn->query('INSERT INTO products (title) VALUES (NULL)');
}, 'Dibi\NotNullConstraintViolationException', "SQLSTATE[23000]: Column 'title' cannot be null", 1048);
Assert::same('INSERT INTO products (title) VALUES (NULL)', $e->getSql());
$e = Assert::exception(function () use ($conn) {
$conn->query('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)');
}, 'Dibi\ForeignKeyConstraintViolationException', '%a% a foreign key constraint fails %a%', 1452);
Assert::same('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)', $e->getSql());

View File

@@ -0,0 +1,41 @@
<?php
/**
* Test: query exceptions.
* @dataProvider ../databases.ini mysql
*/
use Tester\Assert;
require __DIR__ . '/bootstrap.php';
$conn = new Dibi\Connection($config);
$conn->loadFile(__DIR__ . "/data/$config[system].sql");
$e = Assert::exception(function () use ($conn) {
$conn->query('SELECT');
}, 'Dibi\DriverException', "%a% error in your SQL syntax;%a%", 1064);
Assert::same('SELECT', $e->getSql());
$e = Assert::exception(function () use ($conn) {
$conn->query('INSERT INTO products (product_id, title) VALUES (1, "New")');
}, 'Dibi\UniqueConstraintViolationException', "Duplicate entry '1' for key 'PRIMARY'", 1062);
Assert::same("INSERT INTO products (product_id, title) VALUES (1, 'New')", $e->getSql());
$e = Assert::exception(function () use ($conn) {
$conn->query('INSERT INTO products (title) VALUES (NULL)');
}, 'Dibi\NotNullConstraintViolationException', "Column 'title' cannot be null", 1048);
Assert::same('INSERT INTO products (title) VALUES (NULL)', $e->getSql());
$e = Assert::exception(function () use ($conn) {
$conn->query('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)');
}, 'Dibi\ForeignKeyConstraintViolationException', '%a% a foreign key constraint fails %a%', 1452);
Assert::same('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)', $e->getSql());

View File

@@ -0,0 +1,41 @@
<?php
/**
* Test: query exceptions.
* @dataProvider ../databases.ini mysqli
*/
use Tester\Assert;
require __DIR__ . '/bootstrap.php';
$conn = new Dibi\Connection($config);
$conn->loadFile(__DIR__ . "/data/$config[system].sql");
$e = Assert::exception(function () use ($conn) {
$conn->query('SELECT');
}, 'Dibi\DriverException', "%a% error in your SQL syntax;%a%", 1064);
Assert::same('SELECT', $e->getSql());
$e = Assert::exception(function () use ($conn) {
$conn->query('INSERT INTO products (product_id, title) VALUES (1, "New")');
}, 'Dibi\UniqueConstraintViolationException', "Duplicate entry '1' for key 'PRIMARY'", 1062);
Assert::same("INSERT INTO products (product_id, title) VALUES (1, 'New')", $e->getSql());
$e = Assert::exception(function () use ($conn) {
$conn->query('INSERT INTO products (title) VALUES (NULL)');
}, 'Dibi\NotNullConstraintViolationException', "Column 'title' cannot be null", 1048);
Assert::same('INSERT INTO products (title) VALUES (NULL)', $e->getSql());
$e = Assert::exception(function () use ($conn) {
$conn->query('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)');
}, 'Dibi\ForeignKeyConstraintViolationException', '%a% a foreign key constraint fails %a%', 1452);
Assert::same('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)', $e->getSql());

View File

@@ -0,0 +1,41 @@
<?php
/**
* Test: query exceptions.
* @dataProvider ../databases.ini postgre-pdo
*/
use Tester\Assert;
require __DIR__ . '/bootstrap.php';
$conn = new Dibi\Connection($config);
$conn->loadFile(__DIR__ . "/data/$config[system].sql");
$e = Assert::exception(function () use ($conn) {
$conn->query('SELECT INTO');
}, 'Dibi\DriverException', '%a% syntax error %A%');
Assert::same('SELECT INTO', $e->getSql());
$e = Assert::exception(function () use ($conn) {
$conn->query('INSERT INTO products (product_id, title) VALUES (1, "New")');
}, 'Dibi\UniqueConstraintViolationException', '%a% violates unique constraint %A%', '23505');
Assert::same("INSERT INTO products (product_id, title) VALUES (1, 'New')", $e->getSql());
$e = Assert::exception(function () use ($conn) {
$conn->query('INSERT INTO products (title) VALUES (NULL)');
}, 'Dibi\NotNullConstraintViolationException', '%a% null value in column "title" violates not-null constraint%A?%', '23502');
Assert::same('INSERT INTO products (title) VALUES (NULL)', $e->getSql());
$e = Assert::exception(function () use ($conn) {
$conn->query('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)');
}, 'Dibi\ForeignKeyConstraintViolationException', '%a% violates foreign key constraint %A%', '23503');
Assert::same('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)', $e->getSql());

View File

@@ -0,0 +1,41 @@
<?php
/**
* Test: query exceptions.
* @dataProvider ../databases.ini postgre
*/
use Tester\Assert;
require __DIR__ . '/bootstrap.php';
$conn = new Dibi\Connection($config);
$conn->loadFile(__DIR__ . "/data/$config[system].sql");
$e = Assert::exception(function () use ($conn) {
$conn->query('SELECT INTO');
}, 'Dibi\DriverException', 'syntax error %A%');
Assert::same('SELECT INTO', $e->getSql());
$e = Assert::exception(function () use ($conn) {
$conn->query('INSERT INTO products (product_id, title) VALUES (1, "New")');
}, 'Dibi\UniqueConstraintViolationException', '%a% violates unique constraint %A%', '23505');
Assert::same("INSERT INTO products (product_id, title) VALUES (1, 'New')", $e->getSql());
$e = Assert::exception(function () use ($conn) {
$conn->query('INSERT INTO products (title) VALUES (NULL)');
}, 'Dibi\NotNullConstraintViolationException', 'null value in column "title" violates not-null constraint%A%', '23502');
Assert::same('INSERT INTO products (title) VALUES (NULL)', $e->getSql());
$e = Assert::exception(function () use ($conn) {
$conn->query('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)');
}, 'Dibi\ForeignKeyConstraintViolationException', '%a% violates foreign key constraint %A%', '23503');
Assert::same('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)', $e->getSql());

View File

@@ -0,0 +1,42 @@
<?php
/**
* Test: query exceptions.
* @dataProvider ../databases.ini sqlite-pdo
*/
use Tester\Assert;
require __DIR__ . '/bootstrap.php';
$conn = new Dibi\Connection($config);
$conn->loadFile(__DIR__ . "/data/$config[system].sql");
$e = Assert::exception(function () use ($conn) {
$conn->query('SELECT');
}, 'Dibi\DriverException', '%a% syntax error', 1);
Assert::same('SELECT', $e->getSql());
$e = Assert::exception(function () use ($conn) {
$conn->query('INSERT INTO products (product_id, title) VALUES (1, "New")');
}, 'Dibi\UniqueConstraintViolationException', 'SQLSTATE[23000]: %a%', 19);
Assert::same("INSERT INTO products (product_id, title) VALUES (1, 'New')", $e->getSql());
$e = Assert::exception(function () use ($conn) {
$conn->query('INSERT INTO products (title) VALUES (NULL)');
}, 'Dibi\NotNullConstraintViolationException', 'SQLSTATE[23000]: %a%', 19);
Assert::same('INSERT INTO products (title) VALUES (NULL)', $e->getSql());
$e = Assert::exception(function () use ($conn) {
$conn->query('PRAGMA foreign_keys=true');
$conn->query('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)');
}, 'Dibi\ForeignKeyConstraintViolationException', 'SQLSTATE[23000]: %a%', 19);
Assert::same('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)', $e->getSql());

View File

@@ -0,0 +1,42 @@
<?php
/**
* Test: query exceptions.
* @dataProvider ../databases.ini sqlite3
*/
use Tester\Assert;
require __DIR__ . '/bootstrap.php';
$conn = new Dibi\Connection($config);
$conn->loadFile(__DIR__ . "/data/$config[system].sql");
$e = Assert::exception(function () use ($conn) {
$conn->query('SELECT');
}, 'Dibi\DriverException', '%a% syntax error', 1);
Assert::same('SELECT', $e->getSql());
$e = Assert::exception(function () use ($conn) {
$conn->query('INSERT INTO products (product_id, title) VALUES (1, "New")');
}, 'Dibi\UniqueConstraintViolationException', '%a%', 19);
Assert::same("INSERT INTO products (product_id, title) VALUES (1, 'New')", $e->getSql());
$e = Assert::exception(function () use ($conn) {
$conn->query('INSERT INTO products (title) VALUES (NULL)');
}, 'Dibi\NotNullConstraintViolationException', NULL, 19);
Assert::same('INSERT INTO products (title) VALUES (NULL)', $e->getSql());
$e = Assert::exception(function () use ($conn) {
$conn->query('PRAGMA foreign_keys=true');
$conn->query('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)');
}, 'Dibi\ForeignKeyConstraintViolationException', NULL, 19);
Assert::same('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)', $e->getSql());