Passed
Push — master ( 5564a3...f5c0ea )
by Morris
10:35 queued 10s
created

Adapter::insertIgnoreConflict()   A

Complexity

Conditions 3
Paths 8

Size

Total Lines 10
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 8
nop 2
dl 0
loc 10
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Bart Visscher <[email protected]>
6
 * @author Joas Schilling <[email protected]>
7
 * @author Jonny007-MKD <[email protected]>
8
 * @author Morris Jobke <[email protected]>
9
 * @author Robin Appelman <[email protected]>
10
 * @author Thomas Müller <[email protected]>
11
 *
12
 * @license AGPL-3.0
13
 *
14
 * This code is free software: you can redistribute it and/or modify
15
 * it under the terms of the GNU Affero General Public License, version 3,
16
 * as published by the Free Software Foundation.
17
 *
18
 * This program is distributed in the hope that it will be useful,
19
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21
 * GNU Affero General Public License for more details.
22
 *
23
 * You should have received a copy of the GNU Affero General Public License, version 3,
24
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
25
 *
26
 */
27
28
namespace OC\DB;
29
30
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
31
32
/**
33
 * This handles the way we use to write queries, into something that can be
34
 * handled by the database abstraction layer.
35
 */
36
class Adapter {
37
38
	/**
39
	 * @var \OC\DB\Connection $conn
40
	 */
41
	protected $conn;
42
43
	public function __construct($conn) {
44
		$this->conn = $conn;
45
	}
46
47
	/**
48
	 * @param string $table name
49
	 * @return int id of last insert statement
50
	 */
51
	public function lastInsertId($table) {
52
		return $this->conn->realLastInsertId($table);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->conn->realLastInsertId($table) returns the type string which is incompatible with the documented return type integer.
Loading history...
53
	}
54
55
	/**
56
	 * @param string $statement that needs to be changed so the db can handle it
57
	 * @return string changed statement
58
	 */
59
	public function fixupStatement($statement) {
60
		return $statement;
61
	}
62
63
	/**
64
	 * Create an exclusive read+write lock on a table
65
	 *
66
	 * @param string $tableName
67
	 * @since 9.1.0
68
	 */
69
	public function lockTable($tableName) {
70
		$this->conn->beginTransaction();
71
		$this->conn->executeUpdate('LOCK TABLE `' .$tableName . '` IN EXCLUSIVE MODE');
72
	}
73
74
	/**
75
	 * Release a previous acquired lock again
76
	 *
77
	 * @since 9.1.0
78
	 */
79
	public function unlockTable() {
80
		$this->conn->commit();
81
	}
82
83
	/**
84
	 * Insert a row if the matching row does not exists. To accomplish proper race condition avoidance
85
	 * it is needed that there is also a unique constraint on the values. Then this method will
86
	 * catch the exception and return 0.
87
	 *
88
	 * @param string $table The table name (will replace *PREFIX* with the actual prefix)
89
	 * @param array $input data that should be inserted into the table  (column name => value)
90
	 * @param array|null $compare List of values that should be checked for "if not exists"
91
	 *				If this is null or an empty array, all keys of $input will be compared
92
	 *				Please note: text fields (clob) must not be used in the compare array
93
	 * @return int number of inserted rows
94
	 * @throws \Doctrine\DBAL\DBALException
95
	 * @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
96
	 */
97
	public function insertIfNotExist($table, $input, array $compare = null) {
98
		if (empty($compare)) {
99
			$compare = array_keys($input);
100
		}
101
		$query = 'INSERT INTO `' .$table . '` (`'
102
			. implode('`,`', array_keys($input)) . '`) SELECT '
103
			. str_repeat('?,', count($input)-1).'? ' // Is there a prettier alternative?
104
			. 'FROM `' . $table . '` WHERE ';
105
106
		$inserts = array_values($input);
107
		foreach($compare as $key) {
108
			$query .= '`' . $key . '`';
109
			if (is_null($input[$key])) {
110
				$query .= ' IS NULL AND ';
111
			} else {
112
				$inserts[] = $input[$key];
113
				$query .= ' = ? AND ';
114
			}
115
		}
116
		$query = substr($query, 0, -5);
117
		$query .= ' HAVING COUNT(*) = 0';
118
119
		try {
120
			return $this->conn->executeUpdate($query, $inserts);
121
		} catch (UniqueConstraintViolationException $e) {
122
			// if this is thrown then a concurrent insert happened between the insert and the sub-select in the insert, that should have avoided it
123
			// it's fine to ignore this then
124
			//
125
			// more discussions about this can be found at https://github.com/nextcloud/server/pull/12315
126
			return 0;
127
		}
128
	}
129
130
	/**
131
	 * @suppress SqlInjectionChecker
132
	 */
133
	public function insertIgnoreConflict(string $table,array $values) : int {
134
		try {
135
			$builder = $this->conn->getQueryBuilder();
136
			$builder->insert($table);
137
			foreach($values as $key => $value) {
138
				$builder->setValue($key, $builder->createNamedParameter($value));
139
			}
140
			return $builder->execute();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $builder->execute() could return the type Doctrine\DBAL\Driver\Statement which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
141
		} catch(UniqueConstraintViolationException $e) {
142
			return 0;
143
		}
144
	}
145
}
146