mirror of
https://github.com/nextcloud/server.git
synced 2026-02-03 20:41:22 -05:00
Right now our API exports the Doctrine/dbal exception. As we've seen with the dbal 3 upgrade, the leakage of 3rdparty types is problematic as a dependency update means lots of work in apps, due to the direct dependency of what Nextcloud ships. This breaks this dependency so that apps only need to depend on our public API. That API can then be vendor (db lib) agnostic and we can work around future deprecations/removals in dbal more easily. Right now the type of exception thrown is transported as "reason". For the more popular types of errors we can extend the new exception class and allow apps to catch specific errors only. Right now they have to catch-check-rethrow. This is not ideal, but better than the dependnecy on dbal. Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
153 lines
4.8 KiB
PHP
153 lines
4.8 KiB
PHP
<?php
|
|
/**
|
|
* @copyright Copyright (c) 2016, ownCloud, Inc.
|
|
*
|
|
* @author Bart Visscher <bartv@thisnet.nl>
|
|
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
|
* @author Joas Schilling <coding@schilljs.com>
|
|
* @author Jonny007-MKD <1-23-4-5@web.de>
|
|
* @author Morris Jobke <hey@morrisjobke.de>
|
|
* @author Ole Ostergaard <ole.c.ostergaard@gmail.com>
|
|
* @author Ole Ostergaard <ole.ostergaard@knime.com>
|
|
* @author Robin Appelman <robin@icewind.nl>
|
|
* @author Thomas Müller <thomas.mueller@tmit.eu>
|
|
*
|
|
* @license AGPL-3.0
|
|
*
|
|
* This code is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License, version 3,
|
|
* as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License, version 3,
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
*
|
|
*/
|
|
|
|
namespace OC\DB;
|
|
|
|
use Doctrine\DBAL\Exception;
|
|
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.
|
|
*/
|
|
class Adapter {
|
|
|
|
/**
|
|
* @var \OC\DB\Connection $conn
|
|
*/
|
|
protected $conn;
|
|
|
|
public function __construct($conn) {
|
|
$this->conn = $conn;
|
|
}
|
|
|
|
/**
|
|
* @param string $table name
|
|
*
|
|
* @return int id of last insert statement
|
|
* @throws Exception
|
|
*/
|
|
public function lastInsertId($table) {
|
|
return (int) $this->conn->realLastInsertId($table);
|
|
}
|
|
|
|
/**
|
|
* @param string $statement that needs to be changed so the db can handle it
|
|
* @return string changed statement
|
|
*/
|
|
public function fixupStatement($statement) {
|
|
return $statement;
|
|
}
|
|
|
|
/**
|
|
* Create an exclusive read+write lock on a table
|
|
*
|
|
* @param string $tableName
|
|
* @throws Exception
|
|
* @since 9.1.0
|
|
*/
|
|
public function lockTable($tableName) {
|
|
$this->conn->beginTransaction();
|
|
$this->conn->executeUpdate('LOCK TABLE `' .$tableName . '` IN EXCLUSIVE MODE');
|
|
}
|
|
|
|
/**
|
|
* Release a previous acquired lock again
|
|
*
|
|
* @throws Exception
|
|
* @since 9.1.0
|
|
*/
|
|
public function unlockTable() {
|
|
$this->conn->commit();
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
* @param array|null $compare List of values that should be checked for "if not exists"
|
|
* If this is null or an empty array, all keys of $input will be compared
|
|
* Please note: text fields (clob) must not be used in the compare array
|
|
* @return int number of inserted rows
|
|
* @throws Exception
|
|
* @deprecated 15.0.0 - use unique index and "try { $db->insert() } catch (UniqueConstraintViolationException $e) {}" instead, because it is more reliable and does not have the risk for deadlocks - see https://github.com/nextcloud/server/pull/12371
|
|
*/
|
|
public function insertIfNotExist($table, $input, array $compare = null) {
|
|
if (empty($compare)) {
|
|
$compare = array_keys($input);
|
|
}
|
|
$query = 'INSERT INTO `' .$table . '` (`'
|
|
. implode('`,`', array_keys($input)) . '`) SELECT '
|
|
. str_repeat('?,', count($input) - 1).'? ' // Is there a prettier alternative?
|
|
. 'FROM `' . $table . '` WHERE ';
|
|
|
|
$inserts = array_values($input);
|
|
foreach ($compare as $key) {
|
|
$query .= '`' . $key . '`';
|
|
if (is_null($input[$key])) {
|
|
$query .= ' IS NULL AND ';
|
|
} else {
|
|
$inserts[] = $input[$key];
|
|
$query .= ' = ? AND ';
|
|
}
|
|
}
|
|
$query = substr($query, 0, -5);
|
|
$query .= ' HAVING COUNT(*) = 0';
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @throws \OCP\DB\Exception
|
|
*/
|
|
public function insertIgnoreConflict(string $table,array $values) : int {
|
|
try {
|
|
$builder = $this->conn->getQueryBuilder();
|
|
$builder->insert($table);
|
|
foreach ($values as $key => $value) {
|
|
$builder->setValue($key, $builder->createNamedParameter($value));
|
|
}
|
|
return $builder->execute();
|
|
} catch (UniqueConstraintViolationException $e) {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|