Passed
Push — master ( 9af8c0...c15172 )
by Roeland
15:34 queued 11s
created

OC_DB::prepare()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 4
nop 4
dl 0
loc 16
rs 10
c 0
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
/**
34
 * This class manages the access to the database. It basically is a wrapper for
35
 * Doctrine with some adaptions.
36
 */
37
class OC_DB {
38
39
	/**
40
	 * Prepare a SQL query
41
	 * @param string $query Query string
42
	 * @param int|null $limit
43
	 * @param int|null $offset
44
	 * @param bool|null $isManipulation
45
	 * @throws \OC\DatabaseException
46
	 * @return OC_DB_StatementWrapper prepared SQL query
47
	 * @deprecated 21.0.0 Please use \OCP\IDBConnection::getQueryBuilder() instead
48
	 *
49
	 * SQL query via Doctrine prepare(), needs to be execute()'d!
50
	 */
51
	public static function prepare($query , $limit = null, $offset = null, $isManipulation = null) {
52
		$connection = \OC::$server->getDatabaseConnection();
53
54
		if ($isManipulation === null) {
55
			//try to guess, so we return the number of rows on manipulations
56
			$isManipulation = self::isManipulation($query);
57
		}
58
59
		// return the result
60
		try {
61
			$result = $connection->prepare($query, $limit, $offset);
62
		} catch (\Doctrine\DBAL\Exception $e) {
63
			throw new \OC\DatabaseException($e->getMessage());
64
		}
65
		// differentiate between query and manipulation
66
		return new OC_DB_StatementWrapper($result, $isManipulation);
67
	}
68
69
	/**
70
	 * tries to guess the type of statement based on the first 10 characters
71
	 * the current check allows some whitespace but does not work with IF EXISTS or other more complex statements
72
	 *
73
	 * @param string $sql
74
	 * @return bool
75
	 */
76
	public static function isManipulation($sql) {
77
		$sql = trim($sql);
78
		$selectOccurrence = stripos($sql, 'SELECT');
79
		if ($selectOccurrence === 0) {
80
			return false;
81
		}
82
		$insertOccurrence = stripos($sql, 'INSERT');
83
		if ($insertOccurrence === 0) {
84
			return true;
85
		}
86
		$updateOccurrence = stripos($sql, 'UPDATE');
87
		if ($updateOccurrence === 0) {
88
			return true;
89
		}
90
		$deleteOccurrence = stripos($sql, 'DELETE');
91
		if ($deleteOccurrence === 0) {
92
			return true;
93
		}
94
95
		// This is triggered with "SHOW VERSION" and some more, so until we made a list, we keep this out.
96
		// \OC::$server->getLogger()->logException(new \Exception('Can not detect if query is manipulating: ' . $sql));
97
98
		return false;
99
	}
100
101
	/**
102
	 * execute a prepared statement, on error write log and throw exception
103
	 * @param mixed $stmt OC_DB_StatementWrapper,
104
	 *					  an array with 'sql' and optionally 'limit' and 'offset' keys
105
	 *					.. or a simple sql query string
106
	 * @param array $parameters
107
	 * @return OC_DB_StatementWrapper
108
	 * @throws \OC\DatabaseException
109
	 * @deprecated 21.0.0 Please use \OCP\IDBConnection::getQueryBuilder() instead
110
	 */
111
	public static function executeAudited($stmt, array $parameters = []) {
112
		if (is_string($stmt)) {
113
			// convert to an array with 'sql'
114
			if (stripos($stmt, 'LIMIT') !== false) { //OFFSET requires LIMIT, so we only need to check for LIMIT
115
				// TODO try to convert LIMIT OFFSET notation to parameters
116
				$message = 'LIMIT and OFFSET are forbidden for portability reasons,'
117
						 . ' pass an array with \'limit\' and \'offset\' instead';
118
				throw new \OC\DatabaseException($message);
119
			}
120
			$stmt = ['sql' => $stmt, 'limit' => null, 'offset' => null];
121
		}
122
		if (is_array($stmt)) {
123
			// convert to prepared statement
124
			if (! array_key_exists('sql', $stmt)) {
125
				$message = 'statement array must at least contain key \'sql\'';
126
				throw new \OC\DatabaseException($message);
127
			}
128
			if (! array_key_exists('limit', $stmt)) {
129
				$stmt['limit'] = null;
130
			}
131
			if (! array_key_exists('limit', $stmt)) {
132
				$stmt['offset'] = null;
133
			}
134
			$stmt = self::prepare($stmt['sql'], $stmt['limit'], $stmt['offset']);
135
		}
136
		self::raiseExceptionOnError($stmt, 'Could not prepare statement');
137
		if ($stmt instanceof OC_DB_StatementWrapper) {
138
			$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

138
			$result = /** @scrutinizer ignore-deprecated */ $stmt->execute($parameters);
Loading history...
139
			self::raiseExceptionOnError($result, 'Could not execute statement');
140
		} else {
141
			if (is_object($stmt)) {
142
				$message = 'Expected a prepared statement or array got ' . get_class($stmt);
143
			} else {
144
				$message = 'Expected a prepared statement or array got ' . gettype($stmt);
145
			}
146
			throw new \OC\DatabaseException($message);
147
		}
148
		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...
149
	}
150
151
	/**
152
	 * check if a result is an error and throws an exception, works with \Doctrine\DBAL\Exception
153
	 * @param mixed $result
154
	 * @param string $message
155
	 * @return void
156
	 * @throws \OC\DatabaseException
157
	 */
158
	public static function raiseExceptionOnError($result, $message = null) {
159
		if ($result === false) {
160
			if ($message === null) {
161
				$message = self::getErrorMessage();
162
			} else {
163
				$message .= ', Root cause:' . self::getErrorMessage();
164
			}
165
			throw new \OC\DatabaseException($message);
166
		}
167
	}
168
169
	/**
170
	 * returns the error code and message as a string for logging
171
	 * works with DoctrineException
172
	 * @return string
173
	 */
174
	public static function getErrorMessage() {
175
		$connection = \OC::$server->getDatabaseConnection();
176
		return $connection->getError();
177
	}
178
179
	/**
180
	 * Checks if a table exists in the database - the database prefix will be prepended
181
	 *
182
	 * @param string $table
183
	 * @return bool
184
	 * @throws \OC\DatabaseException
185
	 */
186
	public static function tableExists($table) {
187
		$connection = \OC::$server->getDatabaseConnection();
188
		return $connection->tableExists($table);
189
	}
190
}
191