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>tags/v15.0.0beta2
@@ -27,6 +27,8 @@ | |||
namespace OC\DB; | |||
use Doctrine\DBAL\Exception\UniqueConstraintViolationException; | |||
/** | |||
* This handles the way we use to write queries, into something that can be | |||
* 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 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 .= ' HAVING COUNT(*) = 0'; | |||
return $this->conn->executeUpdate($query, $inserts); | |||
try { | |||
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; | |||
} | |||
} | |||
} |
@@ -27,6 +27,8 @@ | |||
namespace OC\DB; | |||
use Doctrine\DBAL\Exception\UniqueConstraintViolationException; | |||
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 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 .= ')'; | |||
return $this->conn->executeUpdate($query, $inserts); | |||
try { | |||
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; | |||
} | |||
} | |||
} |
@@ -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 array $input data that should be inserted into the table (column name => value) |
@@ -104,7 +104,9 @@ interface IDBConnection { | |||
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 array $input data that should be inserted into the table (column name => value) |