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.

246 lines
7.6 KiB

3 years ago
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud, Inc.
  4. *
  5. * @author Andreas Fischer <bantu@owncloud.com>
  6. * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
  7. * @author Bart Visscher <bartv@thisnet.nl>
  8. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  9. * @author Joas Schilling <coding@schilljs.com>
  10. * @author Jörn Friedrich Dreyer <jfd@butonic.de>
  11. * @author Lukas Reschke <lukas@statuscode.ch>
  12. * @author Morris Jobke <hey@morrisjobke.de>
  13. * @author Robin Appelman <robin@icewind.nl>
  14. * @author Thomas Müller <thomas.mueller@tmit.eu>
  15. * @author Vincent Petry <pvince81@owncloud.com>
  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. use OCP\ILogger;
  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. * get MDB2 schema manager
  40. *
  41. * @return \OC\DB\MDB2SchemaManager
  42. */
  43. private static function getMDB2SchemaManager() {
  44. return new \OC\DB\MDB2SchemaManager(\OC::$server->getDatabaseConnection());
  45. }
  46. /**
  47. * Prepare a SQL query
  48. * @param string $query Query string
  49. * @param int|null $limit
  50. * @param int|null $offset
  51. * @param bool|null $isManipulation
  52. * @throws \OC\DatabaseException
  53. * @return OC_DB_StatementWrapper prepared SQL query
  54. *
  55. * SQL query via Doctrine prepare(), needs to be execute()'d!
  56. */
  57. public static function prepare($query , $limit = null, $offset = null, $isManipulation = null) {
  58. $connection = \OC::$server->getDatabaseConnection();
  59. if ($isManipulation === null) {
  60. //try to guess, so we return the number of rows on manipulations
  61. $isManipulation = self::isManipulation($query);
  62. }
  63. // return the result
  64. try {
  65. $result =$connection->prepare($query, $limit, $offset);
  66. } catch (\Doctrine\DBAL\DBALException $e) {
  67. throw new \OC\DatabaseException($e->getMessage());
  68. }
  69. // differentiate between query and manipulation
  70. $result = new OC_DB_StatementWrapper($result, $isManipulation);
  71. return $result;
  72. }
  73. /**
  74. * tries to guess the type of statement based on the first 10 characters
  75. * the current check allows some whitespace but does not work with IF EXISTS or other more complex statements
  76. *
  77. * @param string $sql
  78. * @return bool
  79. */
  80. public static function isManipulation($sql) {
  81. $selectOccurrence = stripos($sql, 'SELECT');
  82. if ($selectOccurrence !== false && $selectOccurrence < 10) {
  83. return false;
  84. }
  85. $insertOccurrence = stripos($sql, 'INSERT');
  86. if ($insertOccurrence !== false && $insertOccurrence < 10) {
  87. return true;
  88. }
  89. $updateOccurrence = stripos($sql, 'UPDATE');
  90. if ($updateOccurrence !== false && $updateOccurrence < 10) {
  91. return true;
  92. }
  93. $deleteOccurrence = stripos($sql, 'DELETE');
  94. if ($deleteOccurrence !== false && $deleteOccurrence < 10) {
  95. return true;
  96. }
  97. return false;
  98. }
  99. /**
  100. * execute a prepared statement, on error write log and throw exception
  101. * @param mixed $stmt OC_DB_StatementWrapper,
  102. * an array with 'sql' and optionally 'limit' and 'offset' keys
  103. * .. or a simple sql query string
  104. * @param array $parameters
  105. * @return OC_DB_StatementWrapper
  106. * @throws \OC\DatabaseException
  107. */
  108. public static function executeAudited($stmt, array $parameters = []) {
  109. if (is_string($stmt)) {
  110. // convert to an array with 'sql'
  111. if (stripos($stmt, 'LIMIT') !== false) { //OFFSET requires LIMIT, so we only need to check for LIMIT
  112. // TODO try to convert LIMIT OFFSET notation to parameters
  113. $message = 'LIMIT and OFFSET are forbidden for portability reasons,'
  114. . ' pass an array with \'limit\' and \'offset\' instead';
  115. throw new \OC\DatabaseException($message);
  116. }
  117. $stmt = ['sql' => $stmt, 'limit' => null, 'offset' => null];
  118. }
  119. if (is_array($stmt)) {
  120. // convert to prepared statement
  121. if (! array_key_exists('sql', $stmt)) {
  122. $message = 'statement array must at least contain key \'sql\'';
  123. throw new \OC\DatabaseException($message);
  124. }
  125. if (! array_key_exists('limit', $stmt)) {
  126. $stmt['limit'] = null;
  127. }
  128. if (! array_key_exists('limit', $stmt)) {
  129. $stmt['offset'] = null;
  130. }
  131. $stmt = self::prepare($stmt['sql'], $stmt['limit'], $stmt['offset']);
  132. }
  133. self::raiseExceptionOnError($stmt, 'Could not prepare statement');
  134. if ($stmt instanceof OC_DB_StatementWrapper) {
  135. $result = $stmt->execute($parameters);
  136. self::raiseExceptionOnError($result, 'Could not execute statement');
  137. } else {
  138. if (is_object($stmt)) {
  139. $message = 'Expected a prepared statement or array got ' . get_class($stmt);
  140. } else {
  141. $message = 'Expected a prepared statement or array got ' . gettype($stmt);
  142. }
  143. throw new \OC\DatabaseException($message);
  144. }
  145. return $result;
  146. }
  147. /**
  148. * saves database schema to xml file
  149. * @param string $file name of file
  150. * @return bool
  151. *
  152. * TODO: write more documentation
  153. */
  154. public static function getDbStructure($file) {
  155. $schemaManager = self::getMDB2SchemaManager();
  156. return $schemaManager->getDbStructure($file);
  157. }
  158. /**
  159. * Creates tables from XML file
  160. * @param string $file file to read structure from
  161. * @return bool
  162. *
  163. * TODO: write more documentation
  164. */
  165. public static function createDbFromStructure($file) {
  166. $schemaManager = self::getMDB2SchemaManager();
  167. return $schemaManager->createDbFromStructure($file);
  168. }
  169. /**
  170. * update the database schema
  171. * @param string $file file to read structure from
  172. * @throws Exception
  173. * @return string|boolean
  174. * @suppress PhanDeprecatedFunction
  175. */
  176. public static function updateDbFromStructure($file) {
  177. $schemaManager = self::getMDB2SchemaManager();
  178. try {
  179. $result = $schemaManager->updateDbFromStructure($file);
  180. } catch (Exception $e) {
  181. \OCP\Util::writeLog('core', 'Failed to update database structure ('.$e.')', ILogger::FATAL);
  182. throw $e;
  183. }
  184. return $result;
  185. }
  186. /**
  187. * remove all tables defined in a database structure xml file
  188. * @param string $file the xml file describing the tables
  189. */
  190. public static function removeDBStructure($file) {
  191. $schemaManager = self::getMDB2SchemaManager();
  192. $schemaManager->removeDBStructure($file);
  193. }
  194. /**
  195. * check if a result is an error and throws an exception, works with \Doctrine\DBAL\DBALException
  196. * @param mixed $result
  197. * @param string $message
  198. * @return void
  199. * @throws \OC\DatabaseException
  200. */
  201. public static function raiseExceptionOnError($result, $message = null) {
  202. if ($result === false) {
  203. if ($message === null) {
  204. $message = self::getErrorMessage();
  205. } else {
  206. $message .= ', Root cause:' . self::getErrorMessage();
  207. }
  208. throw new \OC\DatabaseException($message);
  209. }
  210. }
  211. /**
  212. * returns the error code and message as a string for logging
  213. * works with DoctrineException
  214. * @return string
  215. */
  216. public static function getErrorMessage() {
  217. $connection = \OC::$server->getDatabaseConnection();
  218. return $connection->getError();
  219. }
  220. /**
  221. * Checks if a table exists in the database - the database prefix will be prepended
  222. *
  223. * @param string $table
  224. * @return bool
  225. * @throws \OC\DatabaseException
  226. */
  227. public static function tableExists($table) {
  228. $connection = \OC::$server->getDatabaseConnection();
  229. return $connection->tableExists($table);
  230. }
  231. }