Passed
Push — master ( 63f996...9981ff )
by Morris
30:04 queued 15:15
created

OC_DB::isManipulation()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 22
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 15
nc 5
nop 1
dl 0
loc 22
rs 9.4555
c 1
b 0
f 0
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->getDatabaseConnection());
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
	 * @depreacted 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\DBALException $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
		\OC::$server->getLogger()->logException(new \Exception('Can not detect if query is manipulating: ' . $sql));
107
108
		return false;
109
	}
110
111
	/**
112
	 * execute a prepared statement, on error write log and throw exception
113
	 * @param mixed $stmt OC_DB_StatementWrapper,
114
	 *					  an array with 'sql' and optionally 'limit' and 'offset' keys
115
	 *					.. or a simple sql query string
116
	 * @param array $parameters
117
	 * @return OC_DB_StatementWrapper
118
	 * @throws \OC\DatabaseException
119
	 * @depreacted 21.0.0 Please use \OCP\IDBConnection::getQueryBuilder() instead
120
	 */
121
	public static function executeAudited($stmt, array $parameters = []) {
122
		if (is_string($stmt)) {
123
			// convert to an array with 'sql'
124
			if (stripos($stmt, 'LIMIT') !== false) { //OFFSET requires LIMIT, so we only need to check for LIMIT
125
				// TODO try to convert LIMIT OFFSET notation to parameters
126
				$message = 'LIMIT and OFFSET are forbidden for portability reasons,'
127
						 . ' pass an array with \'limit\' and \'offset\' instead';
128
				throw new \OC\DatabaseException($message);
129
			}
130
			$stmt = ['sql' => $stmt, 'limit' => null, 'offset' => null];
131
		}
132
		if (is_array($stmt)) {
133
			// convert to prepared statement
134
			if (! array_key_exists('sql', $stmt)) {
135
				$message = 'statement array must at least contain key \'sql\'';
136
				throw new \OC\DatabaseException($message);
137
			}
138
			if (! array_key_exists('limit', $stmt)) {
139
				$stmt['limit'] = null;
140
			}
141
			if (! array_key_exists('limit', $stmt)) {
142
				$stmt['offset'] = null;
143
			}
144
			$stmt = self::prepare($stmt['sql'], $stmt['limit'], $stmt['offset']);
145
		}
146
		self::raiseExceptionOnError($stmt, 'Could not prepare statement');
147
		if ($stmt instanceof OC_DB_StatementWrapper) {
148
			$result = $stmt->execute($parameters);
149
			self::raiseExceptionOnError($result, 'Could not execute statement');
150
		} else {
151
			if (is_object($stmt)) {
152
				$message = 'Expected a prepared statement or array got ' . get_class($stmt);
153
			} else {
154
				$message = 'Expected a prepared statement or array got ' . gettype($stmt);
155
			}
156
			throw new \OC\DatabaseException($message);
157
		}
158
		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...
159
	}
160
161
	/**
162
	 * saves database schema to xml file
163
	 * @param string $file name of file
164
	 * @return bool
165
	 *
166
	 * TODO: write more documentation
167
	 */
168
	public static function getDbStructure($file) {
169
		$schemaManager = self::getMDB2SchemaManager();
170
		return $schemaManager->getDbStructure($file);
171
	}
172
173
	/**
174
	 * Creates tables from XML file
175
	 * @param string $file file to read structure from
176
	 * @return bool
177
	 *
178
	 * TODO: write more documentation
179
	 */
180
	public static function createDbFromStructure($file) {
181
		$schemaManager = self::getMDB2SchemaManager();
182
		return $schemaManager->createDbFromStructure($file);
183
	}
184
185
	/**
186
	 * update the database schema
187
	 * @param string $file file to read structure from
188
	 * @throws Exception
189
	 * @return string|boolean
190
	 * @suppress PhanDeprecatedFunction
191
	 */
192
	public static function updateDbFromStructure($file) {
193
		$schemaManager = self::getMDB2SchemaManager();
194
		try {
195
			$result = $schemaManager->updateDbFromStructure($file);
196
		} catch (Exception $e) {
197
			\OCP\Util::writeLog('core', 'Failed to update database structure ('.$e.')', ILogger::FATAL);
198
			throw $e;
199
		}
200
		return $result;
201
	}
202
203
	/**
204
	 * remove all tables defined in a database structure xml file
205
	 * @param string $file the xml file describing the tables
206
	 */
207
	public static function removeDBStructure($file) {
208
		$schemaManager = self::getMDB2SchemaManager();
209
		$schemaManager->removeDBStructure($file);
210
	}
211
212
	/**
213
	 * check if a result is an error and throws an exception, works with \Doctrine\DBAL\DBALException
214
	 * @param mixed $result
215
	 * @param string $message
216
	 * @return void
217
	 * @throws \OC\DatabaseException
218
	 */
219
	public static function raiseExceptionOnError($result, $message = null) {
220
		if ($result === false) {
221
			if ($message === null) {
222
				$message = self::getErrorMessage();
223
			} else {
224
				$message .= ', Root cause:' . self::getErrorMessage();
225
			}
226
			throw new \OC\DatabaseException($message);
227
		}
228
	}
229
230
	/**
231
	 * returns the error code and message as a string for logging
232
	 * works with DoctrineException
233
	 * @return string
234
	 */
235
	public static function getErrorMessage() {
236
		$connection = \OC::$server->getDatabaseConnection();
237
		return $connection->getError();
238
	}
239
240
	/**
241
	 * Checks if a table exists in the database - the database prefix will be prepended
242
	 *
243
	 * @param string $table
244
	 * @return bool
245
	 * @throws \OC\DatabaseException
246
	 */
247
	public static function tableExists($table) {
248
		$connection = \OC::$server->getDatabaseConnection();
249
		return $connection->tableExists($table);
250
	}
251
}
252