diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php index 104fecfbc72976d9cc3ed9508c53f53d7be5654f..234af008734a0f2dad055f1ee2c44094fc83b535 100644 --- a/core/lib/Drupal/Core/Database/Connection.php +++ b/core/lib/Drupal/Core/Database/Connection.php @@ -495,13 +495,14 @@ protected function filterComment($comment = '') { * @return Drupal\Core\Database\StatementInterface * This method will return one of: the executed statement, the number of * rows affected by the query (not the number matched), or the generated - * insert IT of the last query, depending on the value of + * insert ID of the last query, depending on the value of * $options['return']. Typically that value will be set by default or a * query builder and should not be set by a user. If there is an error, * this method will return NULL and may throw an exception if * $options['throw_exception'] is TRUE. * * @throws PDOException + * @throws Drupal\Core\Database\IntegrityConstraintViolationException */ public function query($query, array $args = array(), $options = array()) { @@ -545,7 +546,13 @@ public function query($query, array $args = array(), $options = array()) { // debug information. $query_string = ($query instanceof DatabaseStatementInterface) ? $stmt->getQueryString() : $query; $message = $e->getMessage() . ": " . $query_string . "; " . print_r($args, TRUE); - $exception = new DatabaseExceptionWrapper($message, 0, $e); + // Match all SQLSTATE 23xxx errors. + if (substr($e->getCode(), -6, -3) == '23') { + $exception = new IntegrityConstraintViolationException($message, $e->getCode(), $e); + } + else { + $exception = new DatabaseExceptionWrapper($message, 0, $e); + } throw $exception; } diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php index c2780049fb83b53c32b16f75e2548aac57fb5580..4fd7d0d4c803fe1456cbddf2741b4643afb9c504 100644 --- a/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php +++ b/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php @@ -11,6 +11,7 @@ use Drupal\Core\Database\Connection as DatabaseConnection; use Drupal\Core\Database\DatabaseNotFoundException; use Drupal\Core\Database\StatementInterface; +use Drupal\Core\Database\IntegrityConstraintViolationException; use Locale; use PDO; @@ -132,6 +133,10 @@ public function query($query, array $args = array(), $options = array()) { } catch (PDOException $e) { if ($options['throw_exception']) { + // Match all SQLSTATE 23xxx errors. + if (substr($e->getCode(), -6, -3) == '23') { + $e = new IntegrityConstraintViolationException($e->getMessage(), $e->getCode(), $e); + } // Add additional debug information. if ($query instanceof StatementInterface) { $e->query_string = $stmt->getQueryString(); diff --git a/core/lib/Drupal/Core/Database/IntegrityConstraintViolationException.php b/core/lib/Drupal/Core/Database/IntegrityConstraintViolationException.php new file mode 100644 index 0000000000000000000000000000000000000000..7b8ac43957de1fa8d5898b261952edb0695bd919 --- /dev/null +++ b/core/lib/Drupal/Core/Database/IntegrityConstraintViolationException.php @@ -0,0 +1,18 @@ +<?php + +/** + * @file + * Definition of Drupal\Core\Database\IntegrityConstraintViolationException + */ + +namespace Drupal\Core\Database; + +use RuntimeException; + +/** + * Exception thrown if a query would violate an integrity constraint. + * + * This exception is thrown e.g. when trying to insert a row that would violate + * a unique key constraint. + */ +class IntegrityConstraintViolationException extends RuntimeException implements DatabaseException { } diff --git a/core/lib/Drupal/Core/Database/Query/Merge.php b/core/lib/Drupal/Core/Database/Query/Merge.php index 547a22323b2d90bfa52205378a7d0a0c22b4598c..3d7564afe173bc2bb25ed86894deae9bedb13859 100644 --- a/core/lib/Drupal/Core/Database/Query/Merge.php +++ b/core/lib/Drupal/Core/Database/Query/Merge.php @@ -9,6 +9,7 @@ use Drupal\Core\Database\Database; use Drupal\Core\Database\Connection; +use Drupal\Core\Database\IntegrityConstraintViolationException; use Exception; @@ -423,7 +424,7 @@ public function execute() { $insert->execute(); return self::STATUS_INSERT; } - catch (Exception $e) { + catch (IntegrityConstraintViolationException $e) { // The insert query failed, maybe it's because a racing insert query // beat us in inserting the same row. Retry the select query, if it // returns a row, ignore the error and continue with the update diff --git a/core/lib/Drupal/Core/Lock/DatabaseLockBackend.php b/core/lib/Drupal/Core/Lock/DatabaseLockBackend.php index d877dbab585a5c95e70e569e15f8f4183fecd4ad..b0ee0a3a6e29ee1c71430d27c0caec8fd6f5c499 100644 --- a/core/lib/Drupal/Core/Lock/DatabaseLockBackend.php +++ b/core/lib/Drupal/Core/Lock/DatabaseLockBackend.php @@ -7,7 +7,7 @@ namespace Drupal\Core\Lock; -use Drupal\Core\Database\DatabaseExceptionWrapper; +use Drupal\Core\Database\IntegrityConstraintViolationException; /** * Defines the database lock backend. This is the default backend in Drupal. @@ -53,12 +53,12 @@ public function acquire($name, $timeout = 30.0) { // We never need to try again. $retry = FALSE; } - catch (DatabaseExceptionWrapper $e) { + catch (IntegrityConstraintViolationException $e) { // Suppress the error. If this is our first pass through the loop, - // then $retry is FALSE. In this case, the insert must have failed - // meaning some other request acquired the lock but did not release it. - // We decide whether to retry by checking lock_may_be_available() - // Since this will break the lock in case it is expired. + // then $retry is FALSE. In this case, the insert failed because some + // other request acquired the lock but did not release it. We decide + // whether to retry by checking lockMayBeAvailable(). This will clear + // the offending row from the database table in case it has expired. $retry = $retry ? FALSE : $this->lockMayBeAvailable($name); } // We only retry in case the first attempt failed, but we then broke diff --git a/core/modules/system/lib/Drupal/system/Tests/Database/InvalidDataTest.php b/core/modules/system/lib/Drupal/system/Tests/Database/InvalidDataTest.php index 90a9e18a150d53e1a521f1355cf8897423076604..c60b6d0b2d4e499d143852e31206e25908009030 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Database/InvalidDataTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Database/InvalidDataTest.php @@ -7,6 +7,7 @@ namespace Drupal\system\Tests\Database; +use Drupal\Core\Database\IntegrityConstraintViolationException; use Exception; /** @@ -46,7 +47,7 @@ function testInsertDuplicateData() { ->execute(); $this->fail('Insert succeedded when it should not have.'); } - catch (Exception $e) { + catch (IntegrityConstraintViolationException $e) { // Check if the first record was inserted. $name = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 63))->fetchField();