From fbb63a9cc36064f7dce561d99f98c3e8ad27c467 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 13 Oct 2015 18:08:04 +0200 Subject: [PATCH] implemented DriverException descendants: - ConstraintViolationException - ForeignKeyConstraintViolationException - NotNullConstraintViolationException - UniqueConstraintViolationException --- src/Dibi/Drivers/MySqlDriver.php | 4 +-- src/Dibi/Drivers/MySqliDriver.php | 27 +++++++++++++++-- src/Dibi/Drivers/OracleDriver.php | 22 +++++++++++++- src/Dibi/Drivers/PdoDriver.php | 34 ++++++++++++++------- src/Dibi/Drivers/PostgreDriver.php | 32 ++++++++++++++++++-- src/Dibi/Drivers/Sqlite3Driver.php | 34 +++++++++++++++++++-- src/Dibi/exceptions.php | 34 ++++++++++++++++++++- tests/dibi/exceptions.mysql-pdo.phpt | 41 +++++++++++++++++++++++++ tests/dibi/exceptions.mysql.phpt | 41 +++++++++++++++++++++++++ tests/dibi/exceptions.mysqli.phpt | 41 +++++++++++++++++++++++++ tests/dibi/exceptions.postgre-pdo.phpt | 41 +++++++++++++++++++++++++ tests/dibi/exceptions.postgre.phpt | 41 +++++++++++++++++++++++++ tests/dibi/exceptions.sqlite-pdo.phpt | 42 ++++++++++++++++++++++++++ tests/dibi/exceptions.sqlite.phpt | 42 ++++++++++++++++++++++++++ 14 files changed, 455 insertions(+), 21 deletions(-) create mode 100644 tests/dibi/exceptions.mysql-pdo.phpt create mode 100644 tests/dibi/exceptions.mysql.phpt create mode 100644 tests/dibi/exceptions.mysqli.phpt create mode 100644 tests/dibi/exceptions.postgre-pdo.phpt create mode 100644 tests/dibi/exceptions.postgre.phpt create mode 100644 tests/dibi/exceptions.sqlite-pdo.phpt create mode 100644 tests/dibi/exceptions.sqlite.phpt diff --git a/src/Dibi/Drivers/MySqlDriver.php b/src/Dibi/Drivers/MySqlDriver.php index b1636233..6d2f215e 100644 --- a/src/Dibi/Drivers/MySqlDriver.php +++ b/src/Dibi/Drivers/MySqlDriver.php @@ -157,8 +157,8 @@ class MySqlDriver implements Dibi\Driver, Dibi\ResultDriver $res = @mysql_unbuffered_query($sql, $this->connection); // intentionally @ } - if (mysql_errno($this->connection)) { - throw new Dibi\DriverException(mysql_error($this->connection), mysql_errno($this->connection), $sql); + if ($code = mysql_errno($this->connection)) { + throw MySqliDriver::createException(mysql_error($this->connection), $code, $sql); } elseif (is_resource($res)) { return $this->createResultDriver($res); diff --git a/src/Dibi/Drivers/MySqliDriver.php b/src/Dibi/Drivers/MySqliDriver.php index 9ccb023d..cd10a8a8 100644 --- a/src/Dibi/Drivers/MySqliDriver.php +++ b/src/Dibi/Drivers/MySqliDriver.php @@ -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 @ - if (mysqli_errno($this->connection)) { - throw new Dibi\DriverException(mysqli_error($this->connection), mysqli_errno($this->connection), $sql); + if ($code = mysqli_errno($this->connection)) { + throw self::createException(mysqli_error($this->connection), $code, $sql); } elseif (is_object($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. * @return array diff --git a/src/Dibi/Drivers/OracleDriver.php b/src/Dibi/Drivers/OracleDriver.php index 77bc3880..6e4bef24 100644 --- a/src/Dibi/Drivers/OracleDriver.php +++ b/src/Dibi/Drivers/OracleDriver.php @@ -109,7 +109,7 @@ class OracleDriver implements Dibi\Driver, Dibi\ResultDriver, Dibi\Reflector @oci_execute($res, $this->autocommit ? OCI_COMMIT_ON_SUCCESS : OCI_DEFAULT); $err = oci_error($res); if ($err) { - throw new Dibi\DriverException($err['message'], $err['code'], $sql); + throw self::createException($err['message'], $err['code'], $sql); } elseif (is_resource($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. * @return int|FALSE number of rows or FALSE on error diff --git a/src/Dibi/Drivers/PdoDriver.php b/src/Dibi/Drivers/PdoDriver.php index d06b8eed..147c5358 100644 --- a/src/Dibi/Drivers/PdoDriver.php +++ b/src/Dibi/Drivers/PdoDriver.php @@ -111,22 +111,34 @@ class PdoDriver implements Dibi\Driver, Dibi\ResultDriver if (isset($list[$cmd])) { $this->affectedRows = $this->connection->exec($sql); - - if ($this->affectedRows === FALSE) { - $err = $this->connection->errorInfo(); - throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1], $sql); + if ($this->affectedRows !== FALSE) { + return; } - } else { $res = $this->connection->query($sql); - - if ($res === FALSE) { - $err = $this->connection->errorInfo(); - throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1], $sql); - } else { + if ($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 ? "%'" : "'"); default: - throw new DibiNotImplementedException; + throw new Dibi\NotImplementedException; } } diff --git a/src/Dibi/Drivers/PostgreDriver.php b/src/Dibi/Drivers/PostgreDriver.php index 8b134dbd..fec191d0 100644 --- a/src/Dibi/Drivers/PostgreDriver.php +++ b/src/Dibi/Drivers/PostgreDriver.php @@ -96,7 +96,7 @@ class PostgreDriver implements Dibi\Driver, Dibi\ResultDriver, Dibi\Reflector pg_set_error_verbosity($this->connection, PGSQL_ERRORS_VERBOSE); 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'])) { @@ -137,7 +137,7 @@ class PostgreDriver implements Dibi\Driver, Dibi\ResultDriver, Dibi\Reflector $res = @pg_query($this->connection, $sql); // intentionally @ 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)) { $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. * @return int|FALSE number of rows or FALSE on error diff --git a/src/Dibi/Drivers/Sqlite3Driver.php b/src/Dibi/Drivers/Sqlite3Driver.php index a668ba4f..9256df5b 100644 --- a/src/Dibi/Drivers/Sqlite3Driver.php +++ b/src/Dibi/Drivers/Sqlite3Driver.php @@ -112,8 +112,8 @@ class Sqlite3Driver implements Dibi\Driver, Dibi\ResultDriver } $res = @$this->connection->query($sql); // intentionally @ - if ($this->connection->lastErrorCode()) { - throw new Dibi\DriverException($this->connection->lastErrorMsg(), $this->connection->lastErrorCode(), $sql); + if ($code = $this->connection->lastErrorCode()) { + throw self::createException($this->connection->lastErrorMsg(), $code, $sql); } elseif ($res instanceof \SQLite3Result) { 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. * @return int|FALSE number of rows or FALSE on error diff --git a/src/Dibi/exceptions.php b/src/Dibi/exceptions.php index 5d742a79..01a78832 100644 --- a/src/Dibi/exceptions.php +++ b/src/Dibi/exceptions.php @@ -64,7 +64,7 @@ class DriverException extends Exception /** * PCRE exception. */ -class PcreException extends \Exception +class PcreException extends Exception { 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 +{ +} diff --git a/tests/dibi/exceptions.mysql-pdo.phpt b/tests/dibi/exceptions.mysql-pdo.phpt new file mode 100644 index 00000000..b41a68b4 --- /dev/null +++ b/tests/dibi/exceptions.mysql-pdo.phpt @@ -0,0 +1,41 @@ +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()); diff --git a/tests/dibi/exceptions.mysql.phpt b/tests/dibi/exceptions.mysql.phpt new file mode 100644 index 00000000..78751917 --- /dev/null +++ b/tests/dibi/exceptions.mysql.phpt @@ -0,0 +1,41 @@ +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()); diff --git a/tests/dibi/exceptions.mysqli.phpt b/tests/dibi/exceptions.mysqli.phpt new file mode 100644 index 00000000..8af4775a --- /dev/null +++ b/tests/dibi/exceptions.mysqli.phpt @@ -0,0 +1,41 @@ +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()); diff --git a/tests/dibi/exceptions.postgre-pdo.phpt b/tests/dibi/exceptions.postgre-pdo.phpt new file mode 100644 index 00000000..5b4d2f07 --- /dev/null +++ b/tests/dibi/exceptions.postgre-pdo.phpt @@ -0,0 +1,41 @@ +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()); diff --git a/tests/dibi/exceptions.postgre.phpt b/tests/dibi/exceptions.postgre.phpt new file mode 100644 index 00000000..3c705569 --- /dev/null +++ b/tests/dibi/exceptions.postgre.phpt @@ -0,0 +1,41 @@ +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()); diff --git a/tests/dibi/exceptions.sqlite-pdo.phpt b/tests/dibi/exceptions.sqlite-pdo.phpt new file mode 100644 index 00000000..df447ae8 --- /dev/null +++ b/tests/dibi/exceptions.sqlite-pdo.phpt @@ -0,0 +1,42 @@ +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()); diff --git a/tests/dibi/exceptions.sqlite.phpt b/tests/dibi/exceptions.sqlite.phpt new file mode 100644 index 00000000..cf0d5c2d --- /dev/null +++ b/tests/dibi/exceptions.sqlite.phpt @@ -0,0 +1,42 @@ +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());