Catch UniqueConstraintViolationException inside insertIfNotExist

This is the most common case for the usage of this method.

See also https://github.com/nextcloud/server/issues/12369 and the linked tickets.

Signed-off-by: Morris Jobke <hey@morrisjobke.de>
This commit is contained in:
Morris Jobke 2018-11-09 10:35:12 +01:00
parent da57aaf814
commit 230e93f575
No known key found for this signature in database
GPG Key ID: FE03C3A163FEDE68
4 changed files with 34 additions and 6 deletions

View File

@ -27,6 +27,8 @@
namespace OC\DB; namespace OC\DB;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
/** /**
* This handles the way we use to write queries, into something that can be * This handles the way we use to write queries, into something that can be
* handled by the database abstraction layer. * handled by the database abstraction layer.
@ -79,7 +81,9 @@ class Adapter {
} }
/** /**
* Insert a row if the matching row does not exists. * Insert a row if the matching row does not exists. To accomplish proper race condition avoidance
* it is needed that there is also a unique constraint on the values. Then this method will
* catch the exception and return 0.
* *
* @param string $table The table name (will replace *PREFIX* with the actual prefix) * @param string $table The table name (will replace *PREFIX* with the actual prefix)
* @param array $input data that should be inserted into the table (column name => value) * @param array $input data that should be inserted into the table (column name => value)
@ -111,6 +115,14 @@ class Adapter {
$query = substr($query, 0, -5); $query = substr($query, 0, -5);
$query .= ' HAVING COUNT(*) = 0'; $query .= ' HAVING COUNT(*) = 0';
try {
return $this->conn->executeUpdate($query, $inserts); return $this->conn->executeUpdate($query, $inserts);
} catch (UniqueConstraintViolationException $e) {
// if this is thrown then a concurrent insert happened between the insert and the sub-select in the insert, that should have avoided it
// it's fine to ignore this then
//
// more discussions about this can be found at https://github.com/nextcloud/server/pull/12315
return 0;
}
} }
} }

View File

@ -27,6 +27,8 @@
namespace OC\DB; namespace OC\DB;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
class AdapterSqlite extends Adapter { class AdapterSqlite extends Adapter {
/** /**
@ -50,7 +52,9 @@ class AdapterSqlite extends Adapter {
} }
/** /**
* Insert a row if the matching row does not exists. * Insert a row if the matching row does not exists. To accomplish proper race condition avoidance
* it is needed that there is also a unique constraint on the values. Then this method will
* catch the exception and return 0.
* *
* @param string $table The table name (will replace *PREFIX* with the actual prefix) * @param string $table The table name (will replace *PREFIX* with the actual prefix)
* @param array $input data that should be inserted into the table (column name => value) * @param array $input data that should be inserted into the table (column name => value)
@ -82,6 +86,14 @@ class AdapterSqlite extends Adapter {
$query = substr($query, 0, -5); $query = substr($query, 0, -5);
$query .= ')'; $query .= ')';
try {
return $this->conn->executeUpdate($query, $inserts); return $this->conn->executeUpdate($query, $inserts);
} catch (UniqueConstraintViolationException $e) {
// if this is thrown then a concurrent insert happened between the insert and the sub-select in the insert, that should have avoided it
// it's fine to ignore this then
//
// more discussions about this can be found at https://github.com/nextcloud/server/pull/12315
return 0;
}
} }
} }

View File

@ -240,7 +240,9 @@ class Connection extends ReconnectWrapper implements IDBConnection {
} }
/** /**
* Insert a row if the matching row does not exists. * Insert a row if the matching row does not exists. To accomplish proper race condition avoidance
* it is needed that there is also a unique constraint on the values. Then this method will
* catch the exception and return 0.
* *
* @param string $table The table name (will replace *PREFIX* with the actual prefix) * @param string $table The table name (will replace *PREFIX* with the actual prefix)
* @param array $input data that should be inserted into the table (column name => value) * @param array $input data that should be inserted into the table (column name => value)

View File

@ -104,7 +104,9 @@ interface IDBConnection {
public function lastInsertId($table = null); public function lastInsertId($table = null);
/** /**
* Insert a row if the matching row does not exists. * Insert a row if the matching row does not exists. To accomplish proper race condition avoidance
* it is needed that there is also a unique constraint on the values. Then this method will
* catch the exception and return 0.
* *
* @param string $table The table name (will replace *PREFIX* with the actual prefix) * @param string $table The table name (will replace *PREFIX* with the actual prefix)
* @param array $input data that should be inserted into the table (column name => value) * @param array $input data that should be inserted into the table (column name => value)