1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
|
<?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>
*
* @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;
}
}
}
|