You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

258 lines
8.0 KiB

10 years ago
11 years ago
13 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
11 years ago
11 years ago
11 years ago
  1. <?php
  2. /**
  3. * @author Andreas Fischer <bantu@owncloud.com>
  4. * @author Bart Visscher <bartv@thisnet.nl>
  5. * @author Joas Schilling <nickvergessen@owncloud.com>
  6. * @author Jörn Friedrich Dreyer <jfd@butonic.de>
  7. * @author Morris Jobke <hey@morrisjobke.de>
  8. * @author Robin Appelman <icewind@owncloud.com>
  9. * @author Thomas Müller <thomas.mueller@tmit.eu>
  10. * @author Vincent Petry <pvince81@owncloud.com>
  11. *
  12. * @copyright Copyright (c) 2016, ownCloud, Inc.
  13. * @license AGPL-3.0
  14. *
  15. * This code is free software: you can redistribute it and/or modify
  16. * it under the terms of the GNU Affero General Public License, version 3,
  17. * as published by the Free Software Foundation.
  18. *
  19. * This program is distributed in the hope that it will be useful,
  20. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  21. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  22. * GNU Affero General Public License for more details.
  23. *
  24. * You should have received a copy of the GNU Affero General Public License, version 3,
  25. * along with this program. If not, see <http://www.gnu.org/licenses/>
  26. *
  27. */
  28. /**
  29. * This class manages the access to the database. It basically is a wrapper for
  30. * Doctrine with some adaptions.
  31. */
  32. class OC_DB {
  33. /**
  34. * get MDB2 schema manager
  35. *
  36. * @return \OC\DB\MDB2SchemaManager
  37. */
  38. private static function getMDB2SchemaManager() {
  39. return new \OC\DB\MDB2SchemaManager(\OC::$server->getDatabaseConnection());
  40. }
  41. /**
  42. * Prepare a SQL query
  43. * @param string $query Query string
  44. * @param int $limit
  45. * @param int $offset
  46. * @param bool $isManipulation
  47. * @throws \OC\DatabaseException
  48. * @return OC_DB_StatementWrapper prepared SQL query
  49. *
  50. * SQL query via Doctrine prepare(), needs to be execute()'d!
  51. */
  52. static public function prepare( $query , $limit = null, $offset = null, $isManipulation = null) {
  53. $connection = \OC::$server->getDatabaseConnection();
  54. if ($isManipulation === null) {
  55. //try to guess, so we return the number of rows on manipulations
  56. $isManipulation = self::isManipulation($query);
  57. }
  58. // return the result
  59. try {
  60. $result =$connection->prepare($query, $limit, $offset);
  61. } catch (\Doctrine\DBAL\DBALException $e) {
  62. throw new \OC\DatabaseException($e->getMessage(), $query);
  63. }
  64. // differentiate between query and manipulation
  65. $result = new OC_DB_StatementWrapper($result, $isManipulation);
  66. return $result;
  67. }
  68. /**
  69. * tries to guess the type of statement based on the first 10 characters
  70. * the current check allows some whitespace but does not work with IF EXISTS or other more complex statements
  71. *
  72. * @param string $sql
  73. * @return bool
  74. */
  75. static public function isManipulation( $sql ) {
  76. $selectOccurrence = stripos($sql, 'SELECT');
  77. if ($selectOccurrence !== false && $selectOccurrence < 10) {
  78. return false;
  79. }
  80. $insertOccurrence = stripos($sql, 'INSERT');
  81. if ($insertOccurrence !== false && $insertOccurrence < 10) {
  82. return true;
  83. }
  84. $updateOccurrence = stripos($sql, 'UPDATE');
  85. if ($updateOccurrence !== false && $updateOccurrence < 10) {
  86. return true;
  87. }
  88. $deleteOccurrence = stripos($sql, 'DELETE');
  89. if ($deleteOccurrence !== false && $deleteOccurrence < 10) {
  90. return true;
  91. }
  92. return false;
  93. }
  94. /**
  95. * execute a prepared statement, on error write log and throw exception
  96. * @param mixed $stmt OC_DB_StatementWrapper,
  97. * an array with 'sql' and optionally 'limit' and 'offset' keys
  98. * .. or a simple sql query string
  99. * @param array $parameters
  100. * @return OC_DB_StatementWrapper
  101. * @throws \OC\DatabaseException
  102. */
  103. static public function executeAudited( $stmt, array $parameters = null) {
  104. if (is_string($stmt)) {
  105. // convert to an array with 'sql'
  106. if (stripos($stmt, 'LIMIT') !== false) { //OFFSET requires LIMIT, so we only need to check for LIMIT
  107. // TODO try to convert LIMIT OFFSET notation to parameters
  108. $message = 'LIMIT and OFFSET are forbidden for portability reasons,'
  109. . ' pass an array with \'limit\' and \'offset\' instead';
  110. throw new \OC\DatabaseException($message);
  111. }
  112. $stmt = array('sql' => $stmt, 'limit' => null, 'offset' => null);
  113. }
  114. if (is_array($stmt)) {
  115. // convert to prepared statement
  116. if ( ! array_key_exists('sql', $stmt) ) {
  117. $message = 'statement array must at least contain key \'sql\'';
  118. throw new \OC\DatabaseException($message);
  119. }
  120. if ( ! array_key_exists('limit', $stmt) ) {
  121. $stmt['limit'] = null;
  122. }
  123. if ( ! array_key_exists('limit', $stmt) ) {
  124. $stmt['offset'] = null;
  125. }
  126. $stmt = self::prepare($stmt['sql'], $stmt['limit'], $stmt['offset']);
  127. }
  128. self::raiseExceptionOnError($stmt, 'Could not prepare statement');
  129. if ($stmt instanceof OC_DB_StatementWrapper) {
  130. $result = $stmt->execute($parameters);
  131. self::raiseExceptionOnError($result, 'Could not execute statement');
  132. } else {
  133. if (is_object($stmt)) {
  134. $message = 'Expected a prepared statement or array got ' . get_class($stmt);
  135. } else {
  136. $message = 'Expected a prepared statement or array got ' . gettype($stmt);
  137. }
  138. throw new \OC\DatabaseException($message);
  139. }
  140. return $result;
  141. }
  142. /**
  143. * saves database schema to xml file
  144. * @param string $file name of file
  145. * @param int $mode
  146. * @return bool
  147. *
  148. * TODO: write more documentation
  149. */
  150. public static function getDbStructure($file) {
  151. $schemaManager = self::getMDB2SchemaManager();
  152. return $schemaManager->getDbStructure($file);
  153. }
  154. /**
  155. * Creates tables from XML file
  156. * @param string $file file to read structure from
  157. * @return bool
  158. *
  159. * TODO: write more documentation
  160. */
  161. public static function createDbFromStructure( $file ) {
  162. $schemaManager = self::getMDB2SchemaManager();
  163. $result = $schemaManager->createDbFromStructure($file);
  164. return $result;
  165. }
  166. /**
  167. * update the database schema
  168. * @param string $file file to read structure from
  169. * @throws Exception
  170. * @return string|boolean
  171. */
  172. public static function updateDbFromStructure($file) {
  173. $schemaManager = self::getMDB2SchemaManager();
  174. try {
  175. $result = $schemaManager->updateDbFromStructure($file);
  176. } catch (Exception $e) {
  177. \OCP\Util::writeLog('core', 'Failed to update database structure ('.$e.')', \OCP\Util::FATAL);
  178. throw $e;
  179. }
  180. return $result;
  181. }
  182. /**
  183. * simulate the database schema update
  184. * @param string $file file to read structure from
  185. * @throws Exception
  186. * @return string|boolean
  187. */
  188. public static function simulateUpdateDbFromStructure($file) {
  189. $schemaManager = self::getMDB2SchemaManager();
  190. try {
  191. $result = $schemaManager->simulateUpdateDbFromStructure($file);
  192. } catch (Exception $e) {
  193. \OCP\Util::writeLog('core', 'Simulated database structure update failed ('.$e.')', \OCP\Util::FATAL);
  194. throw $e;
  195. }
  196. return $result;
  197. }
  198. /**
  199. * remove all tables defined in a database structure xml file
  200. * @param string $file the xml file describing the tables
  201. */
  202. public static function removeDBStructure($file) {
  203. $schemaManager = self::getMDB2SchemaManager();
  204. $schemaManager->removeDBStructure($file);
  205. }
  206. /**
  207. * check if a result is an error and throws an exception, works with \Doctrine\DBAL\DBALException
  208. * @param mixed $result
  209. * @param string $message
  210. * @return void
  211. * @throws \OC\DatabaseException
  212. */
  213. public static function raiseExceptionOnError($result, $message = null) {
  214. if($result === false) {
  215. if ($message === null) {
  216. $message = self::getErrorMessage();
  217. } else {
  218. $message .= ', Root cause:' . self::getErrorMessage();
  219. }
  220. throw new \OC\DatabaseException($message, \OC::$server->getDatabaseConnection()->errorCode());
  221. }
  222. }
  223. /**
  224. * returns the error code and message as a string for logging
  225. * works with DoctrineException
  226. * @return string
  227. */
  228. public static function getErrorMessage() {
  229. $connection = \OC::$server->getDatabaseConnection();
  230. return $connection->getError();
  231. }
  232. /**
  233. * Checks if a table exists in the database - the database prefix will be prepended
  234. *
  235. * @param string $table
  236. * @return bool
  237. * @throws \OC\DatabaseException
  238. */
  239. public static function tableExists($table) {
  240. $connection = \OC::$server->getDatabaseConnection();
  241. return $connection->tableExists($table);
  242. }
  243. }