Passed
Push — master ( aeb32e...81302f )
by Christoph
15:20 queued 10s
created

OC_DB   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 200
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 71
dl 0
loc 200
rs 10
c 0
b 0
f 0
wmc 27

10 Methods

Rating   Name   Duplication   Size   Complexity  
A getMDB2SchemaManager() 0 2 1
A prepare() 0 16 3
B executeAudited() 0 38 9
A isManipulation() 0 23 5
A raiseExceptionOnError() 0 8 3
A tableExists() 0 3 1
A getErrorMessage() 0 3 1
A removeDBStructure() 0 3 1
A updateDbFromStructure() 0 9 2
A createDbFromStructure() 0 3 1
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Andreas Fischer <[email protected]>
6
 * @author Arthur Schiwon <[email protected]>
7
 * @author Bart Visscher <[email protected]>
8
 * @author Christoph Wurst <[email protected]>
9
 * @author Joas Schilling <[email protected]>
10
 * @author Jörn Friedrich Dreyer <[email protected]>
11
 * @author Lukas Reschke <[email protected]>
12
 * @author Morris Jobke <[email protected]>
13
 * @author Robin Appelman <[email protected]>
14
 * @author Thomas Müller <[email protected]>
15
 * @author Vincent Petry <[email protected]>
16
 *
17
 * @license AGPL-3.0
18
 *
19
 * This code is free software: you can redistribute it and/or modify
20
 * it under the terms of the GNU Affero General Public License, version 3,
21
 * as published by the Free Software Foundation.
22
 *
23
 * This program is distributed in the hope that it will be useful,
24
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26
 * GNU Affero General Public License for more details.
27
 *
28
 * You should have received a copy of the GNU Affero General Public License, version 3,
29
 * along with this program. If not, see <http://www.gnu.org/licenses/>
30
 *
31
 */
32
33
use OCP\ILogger;
34
35
/**
36
 * This class manages the access to the database. It basically is a wrapper for
37
 * Doctrine with some adaptions.
38
 */
39
class OC_DB {
40
41
	/**
42
	 * get MDB2 schema manager
43
	 *
44
	 * @return \OC\DB\MDB2SchemaManager
45
	 */
46
	private static function getMDB2SchemaManager() {
47
		return new \OC\DB\MDB2SchemaManager(\OC::$server->get(\OC\DB\Connection::class));
48
	}
49
50
	/**
51
	 * Prepare a SQL query
52
	 * @param string $query Query string
53
	 * @param int|null $limit
54
	 * @param int|null $offset
55
	 * @param bool|null $isManipulation
56
	 * @throws \OC\DatabaseException
57
	 * @return OC_DB_StatementWrapper prepared SQL query
58
	 * @deprecated 21.0.0 Please use \OCP\IDBConnection::getQueryBuilder() instead
59
	 *
60
	 * SQL query via Doctrine prepare(), needs to be execute()'d!
61
	 */
62
	public static function prepare($query , $limit = null, $offset = null, $isManipulation = null) {
63
		$connection = \OC::$server->getDatabaseConnection();
64
65
		if ($isManipulation === null) {
66
			//try to guess, so we return the number of rows on manipulations
67
			$isManipulation = self::isManipulation($query);
68
		}
69
70
		// return the result
71
		try {
72
			$result = $connection->prepare($query, $limit, $offset);
73
		} catch (\Doctrine\DBAL\Exception $e) {
74
			throw new \OC\DatabaseException($e->getMessage());
75
		}
76
		// differentiate between query and manipulation
77
		return new OC_DB_StatementWrapper($result, $isManipulation);
78
	}
79
80
	/**
81
	 * tries to guess the type of statement based on the first 10 characters
82
	 * the current check allows some whitespace but does not work with IF EXISTS or other more complex statements
83
	 *
84
	 * @param string $sql
85
	 * @return bool
86
	 */
87
	public static function isManipulation($sql) {
88
		$sql = trim($sql);
89
		$selectOccurrence = stripos($sql, 'SELECT');
90
		if ($selectOccurrence === 0) {
91
			return false;
92
		}
93
		$insertOccurrence = stripos($sql, 'INSERT');
94
		if ($insertOccurrence === 0) {
95
			return true;
96
		}
97
		$updateOccurrence = stripos($sql, 'UPDATE');
98
		if ($updateOccurrence === 0) {
99
			return true;
100
		}
101
		$deleteOccurrence = stripos($sql, 'DELETE');
102
		if ($deleteOccurrence === 0) {
103
			return true;
104
		}
105
106
		// This is triggered with "SHOW VERSION" and some more, so until we made a list, we keep this out.
107
		// \OC::$server->getLogger()->logException(new \Exception('Can not detect if query is manipulating: ' . $sql));
108
109
		return false;
110
	}
111
112
	/**
113
	 * execute a prepared statement, on error write log and throw exception
114
	 * @param mixed $stmt OC_DB_StatementWrapper,
115
	 *					  an array with 'sql' and optionally 'limit' and 'offset' keys
116
	 *					.. or a simple sql query string
117
	 * @param array $parameters
118
	 * @return OC_DB_StatementWrapper
119
	 * @throws \OC\DatabaseException
120
	 * @deprecated 21.0.0 Please use \OCP\IDBConnection::getQueryBuilder() instead
121
	 */
122
	public static function executeAudited($stmt, array $parameters = []) {
123
		if (is_string($stmt)) {
124
			// convert to an array with 'sql'
125
			if (stripos($stmt, 'LIMIT') !== false) { //OFFSET requires LIMIT, so we only need to check for LIMIT
126
				// TODO try to convert LIMIT OFFSET notation to parameters
127
				$message = 'LIMIT and OFFSET are forbidden for portability reasons,'
128
						 . ' pass an array with \'limit\' and \'offset\' instead';
129
				throw new \OC\DatabaseException($message);
130
			}
131
			$stmt = ['sql' => $stmt, 'limit' => null, 'offset' => null];
132
		}
133
		if (is_array($stmt)) {
134
			// convert to prepared statement
135
			if (! array_key_exists('sql', $stmt)) {
136
				$message = 'statement array must at least contain key \'sql\'';
137
				throw new \OC\DatabaseException($message);
138
			}
139
			if (! array_key_exists('limit', $stmt)) {
140
				$stmt['limit'] = null;
141
			}
142
			if (! array_key_exists('limit', $stmt)) {
143
				$stmt['offset'] = null;
144
			}
145
			$stmt = self::prepare($stmt['sql'], $stmt['limit'], $stmt['offset']);
146
		}
147
		self::raiseExceptionOnError($stmt, 'Could not prepare statement');
148
		if ($stmt instanceof OC_DB_StatementWrapper) {
149
			$result = $stmt->execute($parameters);
0 ignored issues
show
Deprecated Code introduced by
The function OC_DB_StatementWrapper::execute() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

149
			$result = /** @scrutinizer ignore-deprecated */ $stmt->execute($parameters);
Loading history...
150
			self::raiseExceptionOnError($result, 'Could not execute statement');
151
		} else {
152
			if (is_object($stmt)) {
153
				$message = 'Expected a prepared statement or array got ' . get_class($stmt);
154
			} else {
155
				$message = 'Expected a prepared statement or array got ' . gettype($stmt);
156
			}
157
			throw new \OC\DatabaseException($message);
158
		}
159
		return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result also could return the type integer which is incompatible with the documented return type OC_DB_StatementWrapper.
Loading history...
160
	}
161
162
	/**
163
	 * Creates tables from XML file
164
	 * @param string $file file to read structure from
165
	 * @return bool
166
	 *
167
	 * TODO: write more documentation
168
	 */
169
	public static function createDbFromStructure($file) {
170
		$schemaManager = self::getMDB2SchemaManager();
171
		return $schemaManager->createDbFromStructure($file);
172
	}
173
174
	/**
175
	 * update the database schema
176
	 * @param string $file file to read structure from
177
	 * @throws Exception
178
	 * @return string|boolean
179
	 * @suppress PhanDeprecatedFunction
180
	 */
181
	public static function updateDbFromStructure($file) {
182
		$schemaManager = self::getMDB2SchemaManager();
183
		try {
184
			$result = $schemaManager->updateDbFromStructure($file);
185
		} catch (Exception $e) {
186
			\OCP\Util::writeLog('core', 'Failed to update database structure ('.$e.')', ILogger::FATAL);
187
			throw $e;
188
		}
189
		return $result;
190
	}
191
192
	/**
193
	 * remove all tables defined in a database structure xml file
194
	 * @param string $file the xml file describing the tables
195
	 */
196
	public static function removeDBStructure($file) {
197
		$schemaManager = self::getMDB2SchemaManager();
198
		$schemaManager->removeDBStructure($file);
199
	}
200
201
	/**
202
	 * check if a result is an error and throws an exception, works with \Doctrine\DBAL\Exception
203
	 * @param mixed $result
204
	 * @param string $message
205
	 * @return void
206
	 * @throws \OC\DatabaseException
207
	 */
208
	public static function raiseExceptionOnError($result, $message = null) {
209
		if ($result === false) {
210
			if ($message === null) {
211
				$message = self::getErrorMessage();
212
			} else {
213
				$message .= ', Root cause:' . self::getErrorMessage();
214
			}
215
			throw new \OC\DatabaseException($message);
216
		}
217
	}
218
219
	/**
220
	 * returns the error code and message as a string for logging
221
	 * works with DoctrineException
222
	 * @return string
223
	 */
224
	public static function getErrorMessage() {
225
		$connection = \OC::$server->getDatabaseConnection();
226
		return $connection->getError();
227
	}
228
229
	/**
230
	 * Checks if a table exists in the database - the database prefix will be prepended
231
	 *
232
	 * @param string $table
233
	 * @return bool
234
	 * @throws \OC\DatabaseException
235
	 */
236
	public static function tableExists($table) {
237
		$connection = \OC::$server->getDatabaseConnection();
238
		return $connection->tableExists($table);
239
	}
240
}
241